Vue2.0 对于数据响应式的实现上是有一些局限性的,比如:
-
无法检测数组和对象的新增;
-
无法检测通过索引改变数组的操作;
针对以上问题,我们一般都会把锅甩给 Object.defineProperty。所以,在Vue 3.0 中,尤大把响应式数据部分弃用了 Object.defineProperty,而使用 Proxy 来代替它。
难道 Object.defineProperty 真的要背这锅么,下面就来分析一下 Object.defineProperty 真的无法监测数组下标的变化吗?
先说结论:
首先,Object.defineProperty
本身是可以监控到数组下标的变化的,只是在 Vue 的实现中,从性能/体验的性价比考虑,便放弃了这个特性。
Object.defineProperty
无法直接监听数组的变化。这是因为 Object.defineProperty
可以拦截对象属性的读取和写入操作,但无法拦截数组的方法调用(如 push
、pop
、shift
、unshift
、splice
、sort
和 reverse
等)。具体来说,Object.defineProperty
可以监听数组的元素的增加和修改,但不能监听数组长度的变化和数组方法的调用。
function defineReactive(data, key, value) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function defineGet() {
console.log(`get key: ${key} value: ${value}`)
return value
},
set: function defineSet(newVal) {
console.log(`set key: ${key} value: ${newVal}`)
value = newVal
}
})
}
function observe(data) {
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key])
})
}
let arr = [1, 2, 3]
observe(arr)
从以上操作的结果,可以看出:我们通过索引改变 arr[1],可以发现触发了 set
,也就是Object.defineProperty
是可以检测到通过索引改变数组的操作的。
但是有一个问题,就是对于数组的一些常用方法如push/pop等,无法触发监听;
Vue 2 对数组的响应式处理
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
const original = arrayProto[method];
Object.defineProperty(arrayMethods, method, {
value: function mutator(...args) {
const result = original.apply(this, args);
// 触发视图更新逻辑
console.log(`Array method ${method} called`);
return result;
},
enumerable: false,
writable: true,
configurable: true
});
});
function observeArray(arr) {
arr.__proto__ = arrayMethods;
}
let arr = [];
observeArray(arr);
arr.push(1); // 会触发自定义的 push 方法
那vue2中如何处理新增的属性和删除的属性 ? 实现原理是什么?
Vue 2 提供了 Vue.set
方法来处理新增属性,使新增的属性能够被响应式系统检测到。
实现原理
- 检测对象:首先,
Vue.set
会检查目标是否是一个对象。 - 针对数组:使用
splice
:使用splice
方法在特定位置插入新元素,从而触发数组的响应式更新。 - 针对对象:递归响应化:接着会递归地使新增属性响应化,即调用内部的
defineReactive
方法。 - 触发依赖更新:最后,手动触发对象的依赖更新,使得视图能够响应变化。
Vue 2 提供了 Vue.delete
方法来处理属性删除,使得删除的属性能够触发视图更新。
实现原理
- 检测对象:首先,
Vue.delete
会检查目标是否是一个对象。 - 删除属性:删除目标对象上的指定属性。
- 触发依赖更新:手动触发对象的依赖更新,使得视图能够响应变化。