浅析 Vue 的数据响应式

519 阅读2分钟

数据响应式是指我们在 JS 中修改页面中的数据时,修改了的数据会实时响应到/出现在页面上。

1. 数据响应式的实现

在了解数据响应式如何实现之前,我们需要了解一些函数。

getter 是一个获取某个特点属性的值的方法。

setter 是一个设定某个属性的值的方法。

Obejct.defineProperty 函数让我们能够精确地添加或修改对象地属性。

// getter 和 setter 的用法
let obj = {
    a: 1,
    get b(){
        return this.a+1;
    },
    set c(value){
        this.a += value
    }
}
console.log(obj.a)      // 1,当前obj 中 a 的值
console.log(obj.b)      // 2,不需要加括号直接调用 b 方法
obj.c = 10
console.log(obj.a)      // 11,通过 c 方法设置 a 的值
console.log(obj.b)      // 12
// Obejct.defineProperty 的用法
Object.defineProperty(obj, 'key', {value: 'hello'});
console.log(obj.key)    // hello

了解了以上的函数后,我们就可以来看看 Vue 的数据响应式是如何实现的。

为了能实时响应数据的改变,我们需要监听数据的状态,当数据发生变化时我们才能响应数据的变化对页面进行修改。

当你把一个对象传入 Vue 实例作为 data 选项时,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。

这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在属性被访问和修改时通知变更。

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

2. 数组和对象的响应式

由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。当一个 property 不在 data 对象上时,Vue 无法检测到它,那么也就不能进行响应。

我们可以通过 Vue.set() 或 vm.$set() 方法向已经创建的实例添加响应式的属性。

let vm = new Vue({
    data:{
        a: 1
    }
})
vm.b = 2                //非响应式
Vue.set(vm, 'b', 2)     //响应式
vm.$set(this, 'b' ,2)   //响应式

3. 一道有趣的题

// html
<div id="app">
    <span class="a">{{obj.a}}</span>
    <span class="b">{{obj.b}}</span>
</div>
//js
let app = new Vue({
    el: '#app',
    data: {
        obj: {
            a: 'a'
        }
    }
})
app.obj.a = 'aa'
app.obj.b = 'bb'

正常来说,此时页面只会出现 aa,因为 obj.b 没有定义,所以是不会出现在页面中的。

但实际我们得到的结果是页面会出现 aa 和 bb,这是因为视图的更新是异步的,Vue 先监听到 obj.a 发生了变化,但 Vue 没有立即更新而是将这个更新任务放到任务队列中。

接着继续运行下一行代码,发现 obj.b 的值也发生了变化(从无到有),于是将 obj.a 和 obj.b 一起更新了,所以 obj.b 出现在了页面中。