Vue2 的响应式
什么是响应式
Vue采用非入侵式响应式系统,数据模型仅仅是普通的JavaScript对象。
当修改数据时,视图会自动进行更新。
如何追踪变化
当把一个普通的JavaScript对象传入Vue实例,作为 data 选项。
Vue将遍历此对象的所有的property,并使用Object.defineProperty 把这些 property 全部转为 getter/setter。
在 property 被访问或者修改时,就会触发getter/setter,通知变更,让Vue能够追踪变化。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中,把“接触”过的数据的 property 记录为依赖。
之后,当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
核心API Object.defineProperty
const data = {}
const name = 'zhangsan'
Object.defineProperty(data, "name", {
get: function () {
console.log('get')
return name
},
set: function (newVal) {
console.log('set')
return newVal
}
})
// test
console.log(data.name) // get zhangsan
data.name = 'lisi' // set
- 多层级对象需要用递归实现深度监听;
限制
由于JavaScript的限制,Vue不能直接检测数组和对象的变化。
需要用一些方法来回避限制,保证它们的响应式。
限制解决
对于对象
- 由于在初始化时,进行getter/setter转化,所以无法检测 property 的添加/移除。
- 单个 property
Vue.set(Object, propertyName, value) / this.$set(Object, propertyName, value)
例:
Vue.set(vm.someObj, 'b', 2) / this.$set(this.someObj, 'b', 2)
- 多个 property
// 代替 Object.assign(this.someObject, { a: 1, b: 2 })
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
- 单个 property
对于数组
- Vue 不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
解决第一类问题
- 当你利用索引直接设置一个数组项时,例如:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// 或者
vm.$set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
解决第二类问题
vm.items.splice(newLength)
Object.defineProperty的缺点(Vue3.0启用Proxy)
- 深度监听需要递归到底,一次性计算量大;
- 无法监听 新增/删除 属性(Vue2解决用Vue.set / Vue.delete);
- 无法原生监听数组,需要特殊处理(重写数组方法,在真正触发方法前,先更新视图)
Proxy的问题
- 兼容性不好,而且无法polyfill