Vue 数据响应式

353 阅读2分钟

什么是数据响应式

在 Vue 中,当 data 中的数据发生改变时,视图会进行更新,这就是数据响应式的概念。Vue 2 则是通过 Object.defineProperty 来实现数据响应式的。

Vue 对 data 的操作

通过代码示例,myData 中的 n 数据类型发生改变,一开始是 {n: 0} ,传给 new Vue 之后立刻变成 {n: (...)} ,之所以表现能和 {n: 0} 一致,是因为 Vue 遍历此对象的所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter ,用于对属性读写的监控。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。

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

data

Object.defineProperty 的问题

由于 JavaScript 的限制,Vue 不能检测数组和对象的增加和删除变化。

对于对象

const vm = new Vue({
    data: {
        a: 1
    }
})

// vm.a 是响应式的

vm.b = 2
// vm.b 是非响应式的

对于需要动态添加根级别的响应式 property ,可以使用 Vue.set(object, propertyName, value) 方法来实现。例如上述案例:

Vue.set(vm.someObject, 'b', 2)

还可以使用它的别名 vm.$set 实例方法:

vm.$set(this.someObject, 'b', 2)

两者的作用:

  • 新增 key
  • 自动创建代理和监听(如果没有创建过)
  • 触发 UI 更新(但不会立刻更新)

对于数组

代码示例 ,当我们利用索引直接设置一个数组项时,Vue 不能检测数组的变动。

应对这个问题,我们依然可以使用 Vue.set(object, propertyName, value)vm.$set

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)

// vm.$set
vm.$set(vm.items, indexOfItem, newValue)

如果有新增或删除操作,例如修改数组长度。综上,应对这两个问题,我们可以使用 splice

vm.items.splice(indexOfItem, 1, newValue)

vm.items.splice(newLength)

对此,Vue 篡改了数组的 API ,参考变更方法 ,这7个 API 都和数组的增加和删除操作有关,调用后会更新 UI 。大概整理一下代码思路:

class VueArray extends Array{
    push(...args){
        const oldLength = this.length // 记录当前数组长度
        super(...args)
        for(let i = oldLength; i < this.length; i++){
            Vue.set(this, i, this[i]) // 通知 Vue 每个新增的 key
        }
    }
}

在新的 API 内去实现自动监听和代理,并更新 UI 。

结语

Vue 2 的数据响应式,通过设置 options.data 。首先,会被 Vue 监听原有数据对象。第二,通过 Vue 实例代理。使用 Object.defineProperty 将所有的 property 全部转为 getter/setter 。每次对 data 的读写操作,都会被 Vue 监听,Vue 在 data 改变时更新 UI 。