Vue 2 的 Object.defineProperty
的局限性
Vue 2 使用 Object.defineProperty
来实现响应式数据,但它有一些明显的局限性:
1. 无法监听属性的添加和删除
-
Object.defineProperty
只能监听已经存在的属性,无法监听动态添加或删除的属性。 -
例如:
const obj = { a: 1 }; Vue.set(obj, 'b', 2); // 需要使用 Vue.set 才能让新属性变成响应式 delete obj.a; // 需要使用 Vue.delete 才能触发更新
2. 无法监听数组的变化
-
Object.defineProperty
无法直接监听数组的变化(如push
、pop
、splice
等操作)。 -
Vue 2 通过重写数组的原型方法来实现对数组的监听:
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) { const result = original.apply(this, args); dep.notify(); // 手动触发更新 return result; }; });
3. 性能问题
Object.defineProperty
需要递归遍历对象的每个属性,将其转换为响应式数据,这在初始化时会有一定的性能开销。- 对于嵌套对象,需要递归监听,性能较差。
Vue 3 的 Proxy
的优势
Vue 3 使用 Proxy
来实现响应式数据,解决了 Vue 2 的许多局限性:
1. 可以监听属性的添加和删除
-
Proxy
可以拦截对象的任意操作,包括属性的添加和删除。 -
例如:
const obj = new Proxy({ a: 1 }, { get(target, key) { console.log(`读取属性 ${key}`); return Reflect.get(target, key); }, set(target, key, value) { console.log(`设置属性 ${key} 为 ${value}`); return Reflect.set(target, key, value); }, deleteProperty(target, key) { console.log(`删除属性 ${key}`); return Reflect.deleteProperty(target, key); } }); obj.b = 2; // 触发 set delete obj.a; // 触发 deleteProperty
2. 可以监听数组的变化
-
Proxy
可以直接监听数组的变化,无需重写数组方法。 -
例如:
const arr = new Proxy([1, 2, 3], { get(target, key) { console.log(`读取数组元素 ${key}`); return Reflect.get(target, key); }, set(target, key, value) { console.log(`设置数组元素 ${key} 为 ${value}`); return Reflect.set(target, key, value); } }); arr.push(4); // 触发 set
3. 性能更好
Proxy
是惰性的,只有在访问属性时才会进行依赖收集,减少了初始化时的性能开销。- 对于嵌套对象,
Proxy
可以按需监听,性能更好。
Vue 2 和 Vue 3 的对比
特性 | Vue 2 (Object.defineProperty ) | Vue 3 (Proxy ) |
---|---|---|
监听属性的添加和删除 | 不支持,需使用 Vue.set 和 Vue.delete | 支持,直接监听 |
监听数组的变化 | 不支持,需重写数组方法 | 支持,直接监听 |
性能 | 初始化时需要递归遍历所有属性,性能较差 | 惰性监听,按需收集依赖,性能更好 |
代码复杂度 | 需要额外处理数组和动态属性 | 代码更简洁,功能更强大 |
结合代码的梳理
Vue 2 的实现(基于 Object.defineProperty
)
function observe(obj) {
for (const key in obj) {
let internalValue = obj[key];
let fns = new Set();
// 递归监听嵌套对象
if (typeof internalValue === 'object' && internalValue !== null) {
observe(internalValue);
}
Object.defineProperty(obj, key, {
get() {
if (activeFunc) {
fns.add(activeFunc);
}
return internalValue;
},
set(val) {
internalValue = val;
// 递归监听新值
if (typeof val === 'object' && val !== null) {
observe(val);
}
fns.forEach(fn => fn());
}
});
}
}
Vue 3 的实现(基于 Proxy
)
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
const dep = getDep(target, key);
dep.depend(); // 依赖收集
const result = Reflect.get(target, key, receiver);
// 递归监听嵌套对象
if (typeof result === 'object' && result !== null) {
return reactive(result);
}
return result;
},
set(target, key, value, receiver) {
const dep = getDep(target, key);
const result = Reflect.set(target, key, value, receiver);
dep.notify(); // 派发更新
return result;
},
deleteProperty(target, key) {
const dep = getDep(target, key);
const result = Reflect.deleteProperty(target, key);
dep.notify(); // 派发更新
return result;
}
});
}
总结
- Vue 2 使用
Object.defineProperty
,无法监听属性的添加和删除,也无法直接监听数组的变化,需要通过Vue.set
和重写数组方法来实现。 - Vue 3 使用
Proxy
,可以监听所有操作(包括属性的添加、删除和数组的变化),性能更好,代码更简洁。 Proxy
是 Vue 3 响应式系统的核心,它使得 Vue 3 的响应式系统更加灵活和强大。