人人都会AI编程

Proxy 代理的核心优势

更新时间:2026-07-09

Vue 3 选择用 Proxy 重构响应式系统,而不是在 Vue 2 的 Object.defineProperty 上修修补补,原因很直接:Proxy 从语言层面解决了旧方案那些“做不到”和“做得别扭”的问题。

1. 能检测到属性的新增和删除

这是 Object.defineProperty 最著名的短板——它只能劫持对象已经存在的属性。如果你给一个响应式对象动态添加新属性,或者删除某个已有属性,Vue 2 是无法感知的,必须用 Vue.setVue.delete 这两个额外的 API 来手动触发更新。

// Vue 2 中这样写不会触发更新
this.user.newProp = 'hello'  // 无效!
delete this.user.age          // 无效!

// 必须改成
this.$set(this.user, 'newProp', 'hello')
this.$delete(this.user, 'age')

而 Proxy 是直接代理整个对象,而不是它的某个属性。你对这个对象做的任何读写、新增、删除操作,都会被 setdeleteProperty 拦截器捕获,无需任何特殊 API,代码回归直觉:

// Vue 3 中直接用,一切正常
state.newProp = 'hello'  // 自动响应
delete state.age          // 自动响应

这个改动对开发者最大的意义是:你不再需要关心某个属性是“一开始就有的”还是“后来加上的”。 写代码时不用思考“这行会不会丢响应式”,心智负担明显降低。

2. 对数组的完整支持

Vue 2 对数组的响应式处理也不够彻底。它只能拦截会改变原数组的几个变异方法(pushpopsplice 等),而对于直接通过索引修改数组元素、或者修改 length 属性,依然无法触发更新:

// Vue 2 中这些操作用不了
this.list[0] = newItem  // 无效
this.list.length = 0     // 无效

// 需要改成
this.$set(this.list, 0, newItem)
this.list.splice(0)

Proxy 对数组和对象的拦截行为完全一致,通过索引赋值、修改长度,都能被正确捕获。数组终于可以当成“正常数组”来用了。

3. 支持更多数据类型

Vue 2 的响应式只能作用于普通对象和数组。如果想用 MapSetWeakMapWeakSet 这些 ES6 数据结构,就必须自己封装,因为 Object.defineProperty 根本无法劫持它们的读写操作。

而 Proxy 的拦截器直接覆盖了 getsethasdeleteProperty 等 13 种底层操作,这让 Vue 3 的响应式系统可以原生支持 MapSet 等数据结构。实际开发中,当你需要存储键值对应关系但希望键不限于字符串,或者需要一个自动去重的集合时,可以直接用 reactive(new Map()),它会像普通对象一样拥有响应式能力。

4. 性能更优:真正的“懒代理”

Vue 2 在初始化一个深层嵌套的对象时,必须递归遍历所有属性,把每个属性都用 Object.defineProperty 转化成 getter/setter。如果对象很大(比如从接口拉回一个几千行的树形数据),这个过程会消耗明显的初始化时间,即使很多深层属性根本不会在页面上渲染。

Proxy 的机制完全不同:它只代理对象本身,不递归处理所有属性。 当你访问 obj.a.b.c 时,Proxy 只会在 get 拦截中判断:如果返回值是个对象,那就对这个子对象再套一层 Proxy 并返回。也就是说,只有真正被访问到的深层属性,才会被转成响应式。这叫 “惰性响应式”,极大地减少了不必要的性能开销,对大体积数据的初始化特别友好。

5. 拦截能力更统一,内部实现更干净

在 Vue 2 中,响应式系统需要为对象和数组分别编写处理逻辑,还要额外维护一个专门处理数组的“钩子列表”,代码结构分散且易遗漏。Proxy 将所有这些行为统一到一组拦截器函数中,无论是数组索引修改,还是对象动态增删,都走同一套 set 拦截逻辑。

这带来的不仅是源码维护性的提升,也让响应式行为变得更加可预测——你不会碰到“数组这样写行,那样写不行”的神秘规则。 只要你在操作被 Proxy 包裹的数据,对它的任何修改都能被兜底捕获。

一言蔽之:Proxy 让 Vue 3 的响应式系统从“我们需要遵守框架的特殊规则”变成了“框架帮你兜底,你写正常的 JavaScript 就行”。这是 Vue 3 在开发者体验上一项意义深远的进步。