Vue2 的 $set 和 Vue3 的 Proxy:一个老前端踩了多年坑才想明白的事
用过 Vue2 好些年你真的懂它为什么需要$set吗Vue3 又为什么换成Proxy这篇不用官方术语堆砌就用我当年真实踩过的坑把这俩的区别掰开揉碎讲清楚。先说个丢人的事前阵子面试对方随口问了句为什么 Vue3 用 Proxy不用 Vue2 那套Object.defineProperty了我当场卡了大概半分钟。Vue2 我用了五年Vue3 也上手做项目了日常$set用得贼溜——但真被问到为什么的时候发现自己只是会用根本没真懂。回来翻了翻源码和文档才把这块理顺。今天借这个机会把自己的理解记下来也算是给当年那个卡壳的自己一个交代。用包租婆查水电费理解 Vue2 的响应式先把 Vue2 的响应式想成包租婆管出租屋租出去每一间房包租婆都单独装了一块电表。哪间房用电了对应那块表就转包租婆就知道这间房状态变了去收电费。但问题来了——你要是新租出去一间房却忘了装电表这间房用再多电包租婆也收不到信号。Vue2 里这个装电表的动作就是Object.defineProperty给每个属性加getter/setter。它在组件初始化时把data里已有的每个属性都装好表。所以你一开始写在data里的字段改了能触发更新没问题但你运行时新加的属性比如后台返回的对象没带某个字段你直接obj.newKey 1这块新房间没装表改了也没人知道视图不更新。这就是当年$set存在的根本原因——它是用来给新房间补装电表的。我当年被坑的两个瞬间第一个this.user.age 18不更新。做中后台那会儿接口返回的用户对象没带age字段。我在mounted里直接this.user.age 18页面死活不动。查了半天才反应过来这个age是运行时加的Vue2 管不到。得用this.$set(this.user, age, 18)才行。后来我学乖了凡是可能后端不给全的对象先Object.assign一个带默认值的模板提前把字段都声明好把坑填在前面。第二个数组下标赋值不生效。有一次this.list[0] newValue视图没反应。我甚至一度怀疑是框架 bug现在想想真丢人。最后发现 Vue2 根本拦不住数组的下标赋值得用splice或者$set。这俩坑看着像我代码写错了其实根子上是同一个——defineProperty的能力边界。Vue2 这套方案到底有哪几个硬伤踩完坑再回头看就通透了。Object.defineProperty有三个绕不开的问题必须预先遍历已知属性。新增、删除属性它收不到信号就是上面新房间没装表。数组拦截不到。push/pop这些方法 Vue2 是重写的算 hack但下标赋值这个原生操作它拦不住。深层对象要递归遍历。嵌套越深初始化时递归成本越高而且是一上来就全量铺开的。Vue3 怎么把这些问题端了的Vue3 换成了Proxy等于把每间房装电表改成了整栋楼装一个中央总闸不管你是新住户、还是把房间拆了重建新增/删除属性、数组下标改值只要有人进出总闸统一拦截、统一通知。所以 Vue3 里普通赋值就行没有$set这层心智负担。Proxy代理的是整个对象get/set/deleteProperty/has全都能拦而且是惰性的——你访问到哪一层它才代理哪一层不用一开始全量遍历。正好把上面三个硬伤一一化解。两段代码对比着看更直观js// Vue2给每个属性单独装电表 function defineReactive(obj, key, val) { Object.defineProperty(obj, key, { get() { dep.depend() // 收集依赖 return val }, set(newVal) { val newVal dep.notify() // 通知更新 } }) }js// Vue3整栋楼交给一个 Proxy 总闸 const reactive (obj) new Proxy(obj, { get(target, key, receiver) { track(target, key) // 收集依赖 const res Reflect.get(target, key, receiver) return typeof res object ? reactive(res) : res // 惰性代理 }, set(target, key, value, receiver) { Reflect.set(target, key, value, receiver) trigger(target, key) // 通知更新 return true } })一句话总结差异Vue2 是逐属性上锁Vue3 是整栋楼门禁。顺带提一句 React——它那边不玩拦截对象这套是让你返回新状态setState/ 不可变更新来触发重渲染完全是另一条路子这里就不展开了。给后来者的几句大实话我熬了几年才明白会用$set不等于懂响应式。真正懂是能回答它为什么存在换 Proxy 解决了什么。所以如果你也在用 Vue别像我一样只停在能跑就行。花一个下午把defineProperty和Proxy这两段最朴素的实现读一遍比你刷十个面试题都值。你们当年有没有被 Vue2 的响应式坑过评论区聊聊让我知道自己不是一个人在战斗。