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
会检查目标是否是一个对象。 - 删除属性:删除目标对象上的指定属性。
- 触发依赖更新:手动触发对象的依赖更新,使得视图能够响应变化。