在 Vue2 中,数组的响应式拦截主要通过 重写数组的变异方法 和 动态代理 实现。以下是其核心机制和实现方式:
一、重写数组的变异方法(核心机制)
Vue2 通过劫持数组的 7 个变异方法 (push
、pop
、shift
、unshift
、splice
、sort
、reverse
),使得这些方法在操作数组时能够触发视图更新。具体实现步骤如下:
-
创建代理方法对象
Vue2 会创建一个新的对象
arrayMethods
,继承自Array.prototype
,并重写上述 7 个方法。例如:iniconst arrayProto = Array.prototype; const arrayMethods = Object.create(arrayProto); const methodsToPatch = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']; methodsToPatch.forEach(method => { const original = arrayProto[method]; arrayMethods[method] = function(...args) { const result = original.apply(this, args); // 触发依赖更新 this.__ob__.dep.notify(); return result; }; });
-
替换数组的原型
当数组被声明为响应式数据时,Vue2 会将其原型指向
arrayMethods
,从而拦截对变异方法的调用:iniif (Array.isArray(value)) { value.__proto__ = arrayMethods; // 替换原型 }
-
处理新增元素的响应式
对于
push
、unshift
、splice
等可能新增元素的方法,Vue2 会递归调用observe
方法,使新增元素也变为响应式:iniif (method === 'push' || method === 'unshift') { inserted = args; // 新增的元素 } else if (method === 'splice') { inserted = args.slice(2); // splice 的第3个参数及之后为新增元素 } if (inserted) observeArray(inserted); // 递归劫持新元素
二、动态代理与 Vue.set
对于 直接通过索引修改数组元素 或 修改数组长度 的操作,Vue2 提供了以下解决方案:
-
Vue.set
或vm.$set
通过显式调用
Vue.set(target, key, value)
,可以强制触发响应式更新:kotlinthis.$set(this.list, index, newValue); // 替换指定索引的值
-
splice
方法的替代性使用 通过
splice
方法修改数组元素,例如:kotlinthis.list.splice(index, 1, newValue); // 替换索引为 `index` 的元素
三、限制与注意事项
-
非变异方法无法触发更新
如
filter
、concat
、slice
等不会改变原数组的方法,需手动替换数组引用:kotlinthis.list = this.list.filter(item => item.id > 0); // 替换整个数组
-
直接修改索引或长度的局限性
直接操作
arr[0] = value
或arr.length = 0
不会触发响应式,必须通过变异方法或Vue.set
实现。
四、源码实现示例
以下是 Vue2 中数组拦截的简化源码逻辑:
ini
// 1. 保存原生数组原型
const arrayProto = Array.prototype;
// 2. 创建代理方法对象
const arrayMethods = Object.create(arrayProto);
// 3. 重写变异方法
methodsToPatch.forEach(method => {
const original = arrayProto[method];
arrayMethods[method] = function(...args) {
const result = original.apply(this, args);
// 触发更新
this.__ob__.dep.notify();
return result;
};
});
// 4. 替换数组原型
function observeArray(arr) {
arr.__proto__ = arrayMethods;
arr.forEach(item => observe(item)); // 递归劫持子元素
}
五、总结
Vue2 通过 重写数组的变异方法 实现对数组的拦截,同时结合 Vue.set
和 splice
方法解决索引修改的响应式问题。开发者需注意避免直接操作索引或长度,并优先使用变异方法或 Vue.set
以确保响应式更新。对于复杂场景,Vue3 的 Proxy
提供了更全面的解决方案。