先记住这个答案
Object.defineProperty 主要围绕已经定义的属性配置 getter/setter;Vue 2 因此需要为对象已有字段安装访问器,并对数组方法作专门处理,新增、删除和部分数组修改存在额外使用约束。Proxy 面向整个对象提供 get、set、deleteProperty、ownKeys 等拦截点,让 Vue 3 更容易观察多种操作。但 Proxy 本身不等于响应式系统,Vue 还负责依赖关联、触发更新与调度,直接操作原始对象也可能绕过代理。
- 属性访问器与对象代理的拦截范围不同
- 新增、删除与枚举可使用不同代理陷阱处理
- 必须通过响应式入口访问,代理不是全局监听器
已有属性的访问器与整个对象的代理
给一个字段定义 getter/setter,可以在读取和赋值时插入逻辑,但没有为新字段预先安装访问器时,普通新增操作不会自动走已有字段的 setter。这也是讨论 Vue 2 对象动态属性时需要提到专用 API 的原因,不应把它误解成 JavaScript 本身不允许添加属性。
Proxy 的 set 可以观察经代理新增字段的赋值,deleteProperty 可以观察删除,ownKeys 可以参与枚举。这些能力便于框架区分读取某个字段和遍历整个对象等不同依赖。数组同样是对象,但 Vue 仍需针对数组、集合、深层对象和迭代语义处理细节,不能简化为换一个构造函数就实现了整个框架。
表格行动态增加字段时要走正确入口
假设表格行最初只有 name,收到后端补充信息后要增加 status。在 Vue 3 中通过 reactive 返回的代理更新这个字段,框架可以观察相关操作;若代码一直持有创建代理之前的原始对象并直接修改它,写操作可能绕过代理入口,不能期待同样的触发效果。
这种问题常见于把原始对象交给第三方库,又在另一处读取代理。应明确数据所有权,尽量在应用层通过响应式对象更新,或者在第三方回调中把变化同步回约定入口。为了调试暂时取得原始对象,不应演变成整个业务到处混用 raw 与 proxy。
const raw = { count: 0 };
const proxy = new Proxy(raw, {
set(target, key, value, receiver) {
console.log('set', key);
return Reflect.set(target, key, value, receiver);
}
});
proxy.count = 1;
raw.count = 2;
console.log(proxy.count);查看输出与解释
set count
2这是可独立运行的 JavaScript 代理示意,不是 Vue 响应式实现。两次写入最终都影响同一个原始对象,但只有通过 proxy 的那一次触发 set 拦截,说明代理不会监控所有绕过它发生的操作。
拦截能力提升不代表没有使用边界
Proxy 只能包裹对象,不能直接代理字符串或数字这样的原始值,所以 Vue 仍需要 ref 等容器接口。对象解构得到一个普通原始值后,后续读取这个局部变量也不再是读取代理属性;这与 Proxy 能观察对象操作并不矛盾。
此外,代理与原始对象的引用身份不同,集合键、第三方实例和不可扩展对象等情况需要按框架 API 约定处理。性能也不能只依据“Proxy 更新”就断言一定更快,实际成本取决于访问模式、依赖规模和渲染工作。面试回答应把能力改善与应用层的正确使用同时说明。
容易答错的地方
- 把 Proxy 等同于自动刷新页面
- 原生 Proxy 只提供拦截机制,不会自行建立 Vue 组件订阅或更新 DOM。框架需要在读取时记录依赖,并在写入后触发相应处理;代码示例里的 console.log 只是展示拦截发生。
- 认为 Vue 3 对所有对象都无条件深度代理
- reactive 有适用目标与代理规则,特殊实例及选择 shallow、markRaw 等 API 时会有不同语义。不能用普通嵌套对象的经验推断所有浏览器对象或第三方类实例的行为。
面试官还会怎么问?
为什么修改原始对象后读取代理也可能看到新值?
代理通常仍以该原始对象为目标,因此后续主动读取可以看到修改后的数据。问题在于那次写入没有经过框架需要的拦截入口,已有订阅是否及时被触发与读取能否看到值是两个问题。
解构 reactive 对象后是不是所有数据都失去响应式?
要看解构得到的是什么。取出的原始值成为普通局部值,不再通过代理字段追踪;取出的嵌套对象可能仍是响应式代理。需要保持字段关联时可以使用 toRef、toRefs 或 getter。
Proxy 能解决对象替换造成的引用问题吗?
不能自动让所有持有旧引用的代码转向新对象。状态容器与引用关系仍需明确设计;如果需要频繁整体替换对象,可以根据需求选用 ref 等更合适的入口。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。