跳到主要内容

Vue 3 为什么使用 Proxy

问题

Vue 3.0 中为什么要使用 Proxy?它相比以前的实现方式有什么改进?

答案

Vue 3 使用 Proxy 替代了 Vue 2 中的 Object.defineProperty 来实现响应式系统,这是一个重大的架构改进。

Vue 2 的实现方式(Object.defineProperty)

function defineReactive(obj: Record<string, any>, key: string, val: any) {
Object.defineProperty(obj, key, {
get() {
console.log(`读取 ${key}: ${val}`);
return val;
},
set(newVal) {
console.log(`设置 ${key}: ${newVal}`);
val = newVal;
}
});
}

const data = { name: 'Vue' };
defineReactive(data, 'name', data.name);
Object.defineProperty 的局限性
  1. 无法检测对象属性的添加和删除 - 需要使用 Vue.set() / Vue.delete()
  2. 无法检测数组索引的变化 - arr[0] = newValue 不会触发更新
  3. 无法检测数组长度的修改 - arr.length = 0 不会触发更新
  4. 需要递归遍历 - 初始化时需要遍历所有属性,性能开销大
  5. 需要单独处理数组 - Vue 2 重写了数组的 7 个方法

Vue 3 的实现方式(Proxy)

function reactive<T extends object>(target: T): T {
return new Proxy(target, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
console.log(`读取 ${String(key)}: ${result}`);
// 如果是对象,递归代理(惰性代理)
if (typeof result === 'object' && result !== null) {
return reactive(result);
}
return result;
},
set(target, key, value, receiver) {
console.log(`设置 ${String(key)}: ${value}`);
return Reflect.set(target, key, value, receiver);
},
deleteProperty(target, key) {
console.log(`删除 ${String(key)}`);
return Reflect.deleteProperty(target, key);
}
});
}

const data = reactive({ name: 'Vue', list: [1, 2, 3] });

// 以下操作都能被检测到
data.name = 'Vue3'; // ✅ 修改属性
data.version = '3.0'; // ✅ 添加新属性
delete data.name; // ✅ 删除属性
data.list[0] = 100; // ✅ 修改数组索引
data.list.push(4); // ✅ 数组方法

Proxy 的优势

特性Object.definePropertyProxy
检测属性添加❌ 需要 Vue.set✅ 原生支持
检测属性删除❌ 需要 Vue.delete✅ 原生支持
检测数组索引变化❌ 不支持✅ 原生支持
检测数组长度变化❌ 不支持✅ 原生支持
惰性代理❌ 初始化递归✅ 访问时代理
代理对象本身❌ 代理属性✅ 代理整个对象
性能初始化开销大初始化快,运行时略慢

核心改进详解

1. 惰性代理(Lazy Proxy)

Vue 2 在初始化时递归遍历所有属性,而 Vue 3 只在访问时才代理嵌套对象:

// Vue 2:初始化时递归所有属性
function observe(obj: object) {
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key]);
if (typeof obj[key] === 'object') {
observe(obj[key]); // 递归
}
});
}

// Vue 3:访问时才代理
const handler: ProxyHandler<object> = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
if (typeof result === 'object' && result !== null) {
return new Proxy(result, handler); // 惰性代理
}
return result;
}
};
性能提升

对于大型对象,Vue 3 的惰性代理可以显著减少初始化时间,因为只有实际访问的属性才会被代理。

2. 完整的拦截能力

Proxy 提供了 13 种拦截操作:

const handler: ProxyHandler<object> = {
get(target, prop, receiver) {}, // 读取属性
set(target, prop, value, receiver) {}, // 设置属性
deleteProperty(target, prop) {}, // 删除属性
has(target, prop) {}, // in 操作符
ownKeys(target) {}, // Object.keys() 等
getOwnPropertyDescriptor(target, prop) {},
defineProperty(target, prop, descriptor) {},
getPrototypeOf(target) {},
setPrototypeOf(target, proto) {},
isExtensible(target) {},
preventExtensions(target) {},
apply(target, thisArg, args) {}, // 函数调用
construct(target, args, newTarget) {} // new 操作符
};

3. 更好的数组支持

const arr = reactive([1, 2, 3]);

// 以下操作在 Vue 3 中都能正确触发更新
arr[0] = 100; // ✅ 索引赋值
arr[10] = 'new'; // ✅ 稀疏数组
arr.length = 1; // ✅ 修改长度
arr.push(4); // ✅ 数组方法

兼容性考虑

浏览器支持

Proxy 是 ES6 特性,无法被 polyfill,因此 Vue 3 不支持 IE11。

如果需要支持 IE11,可以考虑:

  • 继续使用 Vue 2
  • 使用 Vue 3 的 @vue/compat 兼容构建

总结

改进点说明
更全面的响应式可检测属性的添加、删除,数组索引和长度变化
更好的性能惰性代理,减少初始化开销
更简洁的代码不需要 Vue.set/Vue.delete,不需要重写数组方法
更好的 TypeScript 支持Proxy 原生支持泛型

相关链接