[响应式原理01]:Vue2 和 Vue3 响应式的核心差异到底是什么?

3 阅读4分钟

第一轮提问(热身探测):

"Vue 是一个以响应式为核心的框架。请你用精炼的语言概括:Vue2 和 Vue3 的响应式原理核心分别是什么?并从架构或性能角度谈谈,尤雨溪为什么要在 Vue3 中换掉底层 API?换掉之后带来了哪些不可忽视的收益?"

请先自己想一想,再往下看参考答案。

参考答案

一、核心原理:同是"拦截",API 不同

Vue2 和 Vue3 的响应式核心思路是一致的:通过拦截数据的读取(get)和设置(set),自动追踪依赖、触发更新

区别在于底层 API 的选择:

Vue2Vue3
底层 APIObject.definePropertyProxy
拦截粒度属性级别对象级别
初始化时机递归遍历所有属性(急切)访问时才代理(懒代理)
支持动态属性❌(需要 $set
支持数组下标❌(重写原型方法替代)

二、懒代理:Vue3 最核心的性能突破

Vue3 Proxy 的"懒代理"特性值得深说。

Object.defineProperty 有个无法回避的问题:它必须在初始化时就递归遍历 data 里的每一个属性,把所有嵌套对象全部劫持一遍。一个大型表单页面可能有几十个嵌套对象,全部在启动时处理,初始化性能损耗极大

Vue3 的 Proxy 不同。reactive({ a: { b: { c: 1 } } }) 这段代码执行完之后,内部的 abc 并没有立即被代理。只有当你真正去访问 state.a 时,Proxy 的 get 拦截器才会被触发,才会在那一刻对 a 的值做判断,如果它是一个对象,再递归地 reactive 包裹它。

没用到的数据,永远不会产生代理开销。这是 Vue3 响应式性能提升的核心逻辑之一。


三、架构级收益:响应式模块解耦(很多人漏掉的点)

这个点是很多同学答这道题时容易忽略的升维视角:

Vue3 把响应式系统从框架本体中完全抽离,单独发布为一个独立的 npm 包:@vue/reactivity

这意味着什么?你可以完全脱离 Vue 框架,在任何 JS 环境(Node.js 脚本、其他框架、甚至原生项目)里直接 import { reactive, ref, computed, effect } from '@vue/reactivity' 来使用这套响应式系统。这在 Vue2 时代是做不到的——defineProperty 的响应式逻辑和框架代码是深度耦合的。

这既是 Vue3 的工程化飞跃,也是尤雨溪在架构上有意追求"关注点分离"的体现。


四、经典坑:Vue2 为什么不直接拦截数组下标?

面试时经常会被追问这个问题,也是很多人回答不清楚的地方。

很多同学的答案是:"Vue2 不支持拦截数组下标"——这其实是不准确的

准确答案是:Object.defineProperty 在技术上完全可以拦截数组的下标访问(下标本质上就是 '0''1' 这样的字符串属性)。

Vue2 之所以没这么做,是纯粹出于性能考量

一个现实的数组可能有几千个元素,如果用 defineProperty 给每个下标都添加 getter/setter,性能损耗是极其恐怖的。尤雨溪权衡之后,选择了一个更轻量的方案——重写数组的 7 个原型方法pushpopshiftunshiftsplicesortreverse),只在这些方法被调用时触发响应式通知,牺牲了少量场景的响应性(直接赋值给下标不触发),换取了绝大多数场景下的性能稳定。


复盘:一个记忆锚点

如果你想在面试中把这道题说得有层次,可以用这个结构:

  1. 核心相同:都是"拦截 get/set,依赖收集 + 派发更新"
  2. API 不同defineProperty(属性级)→ Proxy(对象级)
  3. 性能差异:急切递归 → 懒代理,初始化开销天壤之别
  4. 架构飞跃:响应式模块解耦,@vue/reactivity 独立发布
  5. 数组处理:不是"不能"拦截,是"不该"拦截(性能考量 → 重写原型方法)

能把第 4 点和第 5 点说出来,这道题就从"及格"升到了"加分"。


下一篇:[[响应式原理02]:Proxy 懒代理如何实现深层嵌套响应式?WeakMap 又在其中扮演了什么角色?]