一、Vue2 的响应式原理
1. 底层依赖:Object.defineProperty
Vue2 在
initData
阶段,会遍历data
对象,用Object.defineProperty
给每个属性加上 getter / setter。getter:依赖收集 → 当模板渲染读取属性时,把当前的渲染函数
Watcher
收集到依赖中。setter:派发更新 → 当数据变化时,通知
Dep
里收集的Watcher
重新执行更新。
2. 特点
能拦截 对象已有属性 的读写。
对于 新增属性 / 删除属性 无法检测,只能用
Vue.set / Vue.delete
。对 数组 的监听有限:重写了数组的 7 个变更方法(push、pop、shift、unshift、splice、sort、reverse),但无法监听通过下标直接修改数组元素。
二、Vue3 的响应式原理
1. 底层依赖:Proxy
Vue3 使用
Proxy
包裹整个对象,统一拦截 对象所有操作(读、写、删、in
判断、遍历等)。可以拦截对象的 内部方法(内部方法 = ECMAScript 规范里的
[[Get]] [[Set]] [[Delete]] [[HasProperty]]
等)。
2. 常见拦截方法
get(target, key, receiver)
→ 依赖收集。set(target, key, value, receiver)
→ 派发更新。deleteProperty(target, key)
→ 删除属性时触发更新。has(target, key)
→in
操作符拦截。ownKeys(target)
→for...in
、Object.keys
拦截。
3. 特点
可以监听 新增 / 删除属性。
可以监听 数组下标修改。
能对
Map
、Set
等原生集合类型进行响应式处理。不需要像 Vue2 那样“递归遍历所有属性”,只有在真正访问属性时才递归代理(懒代理)。
🔹 三、框架应用层面
Vue2 (
defineProperty
)只能拦截对象已有属性的 读写。
其他内部操作(比如
in
、delete
、遍历)拦截不了。
Vue3 (
Proxy
)可以拦截对象的 所有基本操作,包括读写、删除、判断、遍历。
响应式系统更健壮、能力更强。
🔹 四、Vue2 vs Vue3 对比总结
对比点 | Vue2 (Object.defineProperty ) |
Vue3 (Proxy ) |
---|---|---|
拦截范围 | 仅限属性读写 | 支持读写、删除、in 、遍历等所有操作 |
新增/删除属性 | 不能监听,需 Vue.set / Vue.delete |
原生支持 |
数组监听 | 只能通过重写方法,不能拦截下标修改 | 可直接监听下标修改 |
嵌套对象 | 初始化时递归遍历,性能差 | 访问时懒代理,性能好 |
Map/Set | 不支持 | 支持 |
性能 | 初始化慢(深度递归) | 初始化快(按需代理) |
五、面试回答示例(结合应用层+底层原理)
“在 Vue2 里,响应式是通过
Object.defineProperty
来实现的,本质上是给对象已有属性添加 getter/setter,用来做依赖收集和派发更新。但它有局限,比如不能监听新增/删除属性,对数组也只能通过方法重写来监听。Vue3 则是基于
Proxy
实现的,它可以拦截对象的所有内部操作,比如 get/set/delete/has/ownKeys 等,所以不仅能监听属性读写,还能监听新增删除、数组下标修改、for...in 遍历等操作。同时 Vue3 对象是懒代理,只有在访问时才递归代理,性能更优,也支持 Map/Set 这类新数据结构。所以从框架应用层面看,Vue2 的双向数据绑定只能拦截‘现有属性的读写’,而 Vue3 则能拦截对象的基本方法/内部方法,响应式能力更强。”
左边是 Vue2 响应式原理(基于
Object.defineProperty
)右边是 Vue3 响应式原理(基于
Proxy
)
可以清晰看到:
Vue2 需要遍历属性,getter 收集依赖、setter 派发更新;
Vue3 直接通过 Proxy 拦截各种操作,依赖收集和派发更新都集中到
Effect
。
笔试手写:
🔹 Vue2 响应式原理 (Object.defineProperty)
data ↓ Object.defineProperty(遍历每个属性) ↓ ┌─────────── getter ───────────┐ │ 读取数据 → 依赖收集 (Dep) │ └──────────────────────────────┘ ┌─────────── setter ───────────┐ │ 修改数据 → 通知 Watcher 更新 │ └──────────────────────────────┘ ↓ 视图更新 👉 关键点:只能拦截“已有属性”,新增/删除属性和数组下标改动监听不到。
Vue3 响应式原理 (Proxy)
data
↓
Proxy(代理整个对象)
↓
┌─────────── get ───────────┐
│ 读取属性 → 依赖收集 (Effect)│
└────────────────────────────┘
┌─────────── set ───────────┐
│ 修改属性 → 触发更新 │
└────────────────────────────┘
┌──────── delete/has/keys ──┐
│ 删除/判断/遍历 → 可拦截 │
└────────────────────────────┘
↓
组件更新
关键点:能拦截几乎所有操作(新增/删除/遍历/数组下标/Map/Set 等
🔹 面试小技巧
如果面试官要你“画个图说明”,就用这个 ASCII 简版,既能表现出流程,也直观体现了 Vue2/3 的区别。
如果他继续问深一点(比如
Dep
/Effect
是怎么实现的),就说 Vue 内部通过 依赖收集容器(Dep/ReactiveEffect) 把响应式数据和视图更新关联起来,数据变 → 通知依赖 → 触发渲染。