Vue3使用 Proxy 重构了响应式系统,相比 Vue 2 的 Object.defineProperty,它不仅能监听对象属性的增删,还能支持数组和深层嵌套对象。
那么,vue3中的reactive函数,内部使用的 Proxy 是如何实现嵌套对象的响应式代理的呢,下边从实际代码角度进行分析。
1. 最简 Proxy 响应式流程
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
console.log(`GET ${key}`);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log(`SET ${key} = ${value}`);
return Reflect.set(target, key, value, receiver);
}
});
}
const obj = reactive({ name: 'Alice' });
console.log(obj.name) // GET name
obj.name = 'Bob'; // SET name = Bob
上边的代码仅能拦截顶层属性的读写, 无法处理嵌套对象:
const state = reactive({
user: { name: 'Alice' }
});
state.user.name = 'Bob'; // 不会触发任何set日志
因为 state.user 返回的是原始对象,不是 Proxy
2. 如何让嵌套对象也变成响应式,可能大家想到的是使用vue2中的类似方案对对象进行递归响应式代理:
function defineReactive(obj, key, val) {
// 递归观测子对象!
if (typeof val === 'object' && val !== null) {
observe(val); // 递归调用 observe
}
Object.defineProperty(obj, key, {
get() { /* 依赖收集 */ },
set(newVal) { /* 触发更新 */ }
});
}
function deepReactive(obj) {
for (let key in obj) {
if (typeof obj[key] === 'object') {
obj[key] = deepReactive(obj[key]); // 递归代理
}
}
return reactive(obj);
}
缺点:
- 性能差:大对象初始化慢
- 内存浪费:未访问的属性也被代理
3. Vue3 的解法:懒递归代理
只在属性被访问时,才将其值转为响应式。
在 get 拦截器中,对返回的对象值再次调用 reactive。
步骤 1:定义 isObject
function isObject(val) {
return val !== null && typeof val === 'object';
}
步骤 2:使用 WeakMap 缓存已代理对象
避免对同一个对象重复创建 Proxy:
const reactiveMap = new WeakMap();
function createReactiveObject(target, handlers) {
if (!isObject(target)) return target;
// 如果已经代理过,直接返回缓存
if (reactiveMap.has(target)) {
return reactiveMap.get(target);
}
const proxy = new Proxy(target, handlers);
reactiveMap.set(target, proxy); // 原始对象 → 代理对象
return proxy;
}
步骤 3:在 get 中递归代理
function createGetter() {
return function get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
// 如果取到的是对象,就把它也变成响应式的
if (isObject(res)) {
return reactive(res);
}
return res;
};
}
function createSetter() {
return function set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
};
}
const mutableHandlers = {
get: createGetter(),
set: createSetter()
};
function reactive(target) {
return createReactiveObject(target, mutableHandlers);
}
4. 测试:嵌套对象也能响应!
const state = reactive({
user: {
profile: {
name: 'Alice'
}
}
});
console.log(state.user.profile.name);
// 输出: Alice
// 内部调用链:
// get user → reactive(user)
// get profile → reactive(profile)
// get name → 'Alice'
4. 注意事项
4.1 为什么用 WeakMap?
- key 必须是对象
- 不阻止垃圾回收(当原始对象无引用时,缓存自动消失)
- 完美匹配 “原始对象 到代理对象” 的映射需求
4.2 为什么不一开始就代理所有属性
- 性能优化:只代理被访问的部分
5. 总结
关键点说明
懒代理只在属性被访问时才代理其值,提升性能
WeakMap 缓存避免同一对象多次代理,防止内存泄漏
isObject 判断必须包含数组等引用类型
702



被折叠的 条评论
为什么被折叠?



