2026年前端面试的镇场题。面试官要看你是真的理解Vue 2 defineProperty的具体局限、Proxy从根本上更好——不是简单的语法升级。
先讲Vue 2的局限:Object.defineProperty把对象每个已有属性转换成getter/setter。问题:检测不到属性增删;数组下标赋值和.length修改拦不住;创建时要递归遍历整个对象树,性能差。
Vue 2的workaround:Vue.set/this.$set给新属性、打补丁的7个数组方法(push/pop/splice等)手动触发更新。补丁式不完整。
Vue 3的Proxy:用一个handler包整个对象,拦截所有操作——get/set/has/deleteProperty/ownKeys。不需要预处理,属性增删、数组下标、Map/Set变更都天然响应。
惰性深响应:Proxy不递归进嵌套对象。只有访问到某个嵌套对象的getter时,Vue才把它包一层新Proxy返回。大对象启动成本大幅降低。
track和trigger是核心函数:getter里track()记录"当前effect(computed、watch、render函数)依赖这个属性";setter里trigger()查出依赖并重新执行。ref vs reactive:reactive给对象返回Proxy;ref把原始值包成{value}让Proxy机制能拦截。
Vue 2用Object.defineProperty做响应式。组件创建时,Vue遍历data的每个属性,替换成getter/setter,读时dep.depend()、写时dep.notify()。这套有具体问题。第一,检测不到属性增删——this.user.age = 20在age原本不存在时完全不触发更新。第二,拦不住数组下标和.length——arr[0] = x、arr.length = 0都不响应,所以Vue 2给7个数组方法打补丁(push、pop、splice等)手动触发。第三,创建时要递归整个对象树,大对象启动非常慢。Vue 3用Proxy。Proxy是原生JS特性,用一个handler把对象包起来,拦截所有操作——get、set、has、deleteProperty、ownKeys全覆盖。不需要预处理。get陷阱里调track(target, key)记录"当前活跃的effect依赖这个属性";set陷阱里调trigger(target, key)通知所有跟踪过这个属性的effect。属性增删、数组下标、Map/Set变更全部天然捕获。Proxy还是惰性的——顶层对象被包,访问到某个嵌套属性时Vue才把嵌套值再包一层Proxy返回。也就是说只有真正触达的路径才会有响应式开销,大state树启动成本骤降。两个主要API:reactive(obj)给对象返回Proxy。ref(value)处理原始值——原始值不能被Proxy包,所以ref返回{value: x}外面套响应式,访问.value会被拦截。模板里ref会自动解包,所以模板里写refName而不是refName.value,但script代码里必须写.value,这是最常见的踩坑点。track和trigger是底层原语,computed、watch、render effect本质上都是effect()——在读时注册依赖,写时重新运行。
一定要和Vue 2对比着讲,这是面试官真正想听的。
讲到惰性深响应是读过源码的信号。
能解释"原始值不能被Proxy所以ref要包.value"是常见追问。
临场忘了track/trigger,即答侠可以实时提示。
IE11不支持Proxy。Vue 2要兼容IE11所以只能用defineProperty。Vue 3放弃了IE11才换成Proxy。
reactive给对象,ref给任意值但主要用于原始值。模板里ref自动解包不用写.value,script里必须写.value。
跳过嵌套的惰性包装,只顶层响应。适合大块不可变数据整体替换的场景,省掉深层包装开销。