目录
二、核心 API:Object.defineProperty
一、什么是数据劫持?
数据劫持指的是拦截对象属性的访问和修改操作的能力。Vue2 通过 JavaScript 的 Object.defineProperty
API 实现这一机制,在属性被读取或修改时执行自定义逻辑。
二、核心 API:Object.defineProperty
Object.defineProperty
允许我们精确控制对象属性的行为:
let obj = { name: 'Vue' };
let value = obj.name;
Object.defineProperty(obj, 'name', {
get() {
console.log('读取 name 属性');
return value;
},
set(newVal) {
console.log('设置 name 属性', newVal);
value = newVal;
}
});
obj.name; // 控制台输出: "读取 name 属性"
obj.name = 'React'; // 控制台输出: "设置 name 属性 React"
三、Vue2 中的数据劫持实现
1. 对象属性的劫持
Vue 在初始化时会遍历 data
中的所有属性:
function defineReactive(obj, key) {
let value = obj[key];
Object.defineProperty(obj, key, {
get() {
console.log(`读取 ${key}`);
return value;
},
set(newVal) {
console.log(`更新 ${key}`, newVal);
value = newVal;
// 这里会触发视图更新
}
});
}
const data = { message: 'Hello Vue' };
defineReactive(data, 'message');
2. 嵌套对象的处理
Vue 递归劫持嵌套对象:
function observe(data) {
if (typeof data !== 'object' || data === null) return;
new Observer(data);
}
class Observer {
constructor(value) {
this.value = value;
this.walk();
}
walk() {
Object.keys(this.value).forEach(key => {
defineReactive(this.value, key);
});
}
}
function defineReactive(obj, key) {
let value = obj[key];
observe(value); // 递归劫持子属性
Object.defineProperty(obj, key, {
// getter/setter 略
});
}
3. 数组的特殊处理
由于 Object.defineProperty
无法检测数组索引变化,Vue 重写了数组方法:
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
const original = arrayProto[method];
arrayMethods[method] = function(...args) {
console.log(`数组执行 ${method} 操作`);
const result = original.apply(this, args);
// 获取新增的元素
let inserted;
switch(method) {
case 'push':
case 'unshift':
inserted = args;
break;
case 'splice':
inserted = args.slice(2);
break;
}
// 劫持新增元素
if (inserted) observeArray(inserted);
// 触发视图更新
return result;
};
});
function observeArray(items) {
for (let item of items) {
observe(item);
}
}
四、结合依赖收集的完整流程
初始化阶段:
遍历
data
属性,通过defineProperty
设置 getter/setter递归处理嵌套对象和数组
依赖收集:
get() { if (Dep.target) { // 当前正在计算的 Watcher dep.depend(); // 将 Watcher 添加到依赖列表 } return value; }
3.派发更新:
set(newVal) { value = newVal; dep.notify(); // 通知所有 Watcher 更新 }
五、数据劫持的局限性
无法检测属性添加/删除
this.obj.newProperty = 'value' // 非响应式
2. 数组索引和长度修改:
this.arr[0] = 'new' // 无法检测
this.arr.length = 10 // 无法检测
3.解决方案:
this.$set(this.obj, 'newProperty', 'value')
this.$delete(this.obj, 'oldProperty')
六、Vue3 的改进方案
Vue3 使用 Proxy
替代 Object.defineProperty
:
可直接监听整个对象而非属性
支持动态添加属性
完美监听数组变化
性能更优
const proxy = new Proxy(obj, { get(target, key) { // 拦截读取操作 }, set(target, key, value) { // 拦截设置操作 } });
总结
Vue2 的数据劫持机制通过 Object.defineProperty
实现,结合依赖收集和派发更新,构建了响应式系统的核心。虽然存在一些局限性,但理解其原理有助于我们:
更好地使用 Vue 的响应式功能
避免常见的响应式陷阱
深入理解 Vue 的设计思想
掌握这些原理,能让你在 Vue 开发中更加得心应手,写出更高效、可维护的代码!