目录
一、什么是数据劫持?
二、核心 API:Object.defineProperty
三、Vue2 中的数据劫持实现
1. 对象属性的劫持
2. 嵌套对象的处理
3. 数组的特殊处理
四、结合依赖收集的完整流程
五、数据劫持的局限性
六、Vue3 的改进方案
总结
一、什么是数据劫持?
数据劫持指的是拦截对象属性的访问和修改操作的能力。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) { // 当前正在计算的 Watcherdep.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 开发中更加得心应手,写出更高效、可维护的代码!