Vue响应式原理解析(Vue2.x和3.x及区别)

我们都知道在Vue.js中,MVVM相对传统的DOM操作,有一个很大的优点就是能实现数据的双向绑定。

先用代码来体验一下:

<div id="app">
        {{message}} {{name}}
    </div>
    <script src="vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                message: 'Hello',
                name: 'vue'
            }
        })
    </script>

运行结果:

在这里插入图片描述

现在来改变一下其中的一个数据,在控制台输入app.name = ‘world’ ,我们可以看到此时运行结果变成了:
在这里插入图片描述

要理解Vue是如何实现数据双向绑定的,就要搞懂两个问题:

①app.name修改数据,Vue内部是如何监听name数据的改变的?

②当数据发生改变时,Vue是如何知道要通知谁,并且实现界面刷新?


我们先来解决第一个问题。

在ES5中有一个Object.defineProperty()方法,能监听各个属性的get和set方法(也称为数据劫持)。该方法有三个参数,前两个参数分别是要监听的数据及其属性,第三个参数是对get和set方法的监听。

const obj = {
    message:'Hello',
    name:'world'
}

//遍历获得每个属性值
Object.keys(obj).forEach(key => {
    let value = obj[key]
})

Object.defineProperty(obj,key,{
    set(newValue){
        console.log('监听' + key + 改变);
        value = newValue
    },
    get(){
        console.log('监听' + key + '对应的值')return value
    }
})

//obj.name = 'vue'
//监听name改变
//obj.message
//监听message对应的值

通过defineProperty()方法,只要数据有任何改变,都可以监听和劫持。但是这些属性的变化和监听,要告诉谁呢,接下来看看第二个问题是怎么解决的。


Vue数据双向绑定原理实现是靠发布-订阅者模式,给需要变化的数据增加一个观察者,将新值和旧值进行对比,如果数据发生变化就采取相应的方法。

//发布订阅者
class Dep{
    constructor(){
        //订阅的数组
        this.subs = []
    }
    //增加订阅者
    addSub(watcher){
        this.subs.push(watcher)
    }
    //通知更新
    notify(){
        this.subs.forEach(item => {
            item.update()
        })
    }
}
//订阅者
class Watcher {
    constructor(name){
        this.name = name
    }
    
    update(){
        console.log(this.name + '发生update')
    }
}

const dep = new Dep()

const watcher = new Watcher('张三')
dep.addSub(watcher)

dep.notify()//张三发生update

还是这张图,我们根据这张图来做一个小结:

Vue响应式原理解析(Vue2.x和3.x及区别)_第1张图片

  1. 创建一个Vue实例,将Vue实例中的data数据传送给Obverse,在Obverse中用Object.defineProperty()方法对各个属性进行监听。同时创建Dep对象,一个属性对应一个Dep对象。Dep里调用addSub方法增加订阅者。

  2. 将el模板传送给Compile,解析el模板中的指令,一个指令对应创建一个Watcher,然后这个Watcher会指向对应属性的Dep对象。第一次创建Vue实例时,会初始化视图,在View中显示第一次的属性。

  3. 这时数据发生了改变,例如改变message属性,Obverse监听到数据发生了改变,就会在Dep对象里调用notify()方法,通知Watcher,Watcher会调用update方法对View视图进行更新,从而实现了数据的响应。


而在Vue 3.x中,将数据劫持中的Object.defineProperty方法改成了用ES6中的Proxy。

Proxy可以监听整个对象,省去遍历提升效率。我们来看看是怎么实现的:

let arr = [1,2,3]
let p = new Proxy(arr,{
    //三个参数,第一个参数是目标对象,第二个参数是get方法,第三个参数是set方法
    get(data,property,receiver){
        // 1.目标对象
        // 2.被获取的属性值
        // 3.Proxy或继承Proxy
        console.log('get被调用了')
    }
    set(data,property,value,receiver){
    	console.log('数组内部发生了变化,更新后的值为:' + value)
    	
	}
})

//p.[1] = 5; //数组内部发生了变化,更新后的值为:5

那两种方法对比,Proxy有什么优势呢?

  1. 用Object.defineProperty()方法无法监听到数组内部的数据变化来实现内部数据的检测。(但是用concat()方法可以)而用Proxy可以监听到数组内部的变化,也可以直接监听对象而非属性。

  2. Proxy有多种拦截方法,如apply,deleteProperty等等,是Object.defineProperty()不具备的。

  3. **Proxy是返回值是一个对象,可以直接进行操作,**而defineProperty()要先遍历所有对象属性值才能进行操作

    但是相对来说,Object.defineProperty()兼容性高一些。

你可能感兴趣的:(Vue)