Vue2 和 Vue3 响应式原理的核心差异在于底层实现 API (Vue2 用 Object.defineProperty,Vue3 用 Proxy + Reflect),这直接导致了两者在覆盖范围、性能、扩展性上的本质区别,以下是系统化的对比和解读:
一、核心底层 API 差异(根本原因)
| 维度 | Vue2 | Vue3 |
|---|---|---|
| 核心 API | Object.defineProperty |
Proxy + Reflect |
| 监听目标 | 监听对象的单个属性 | 监听整个对象/数组 |
| 初始化方式 | 递归遍历对象所有属性,逐个定义 get/set | 对对象做一层代理,访问属性时懒递归(按需监听) |
1. Vue2:Object.defineProperty 实现逻辑
javascript
// Vue2 响应式核心模拟
function defineReactive(obj, key, val) {
// 递归处理嵌套对象
observe(val);
Object.defineProperty(obj, key, {
get() {
// 依赖收集:收集当前属性对应的更新函数(watcher)
dep.depend();
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
// 新值是对象则递归监听
observe(newVal);
// 触发更新:通知所有依赖的 watcher 执行
dep.notify();
}
});
}
// 遍历对象所有属性,逐个绑定
function observe(obj) {
if (typeof obj !== 'object' || obj === null) return;
Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key]));
}
2. Vue3:Proxy + Reflect 实现逻辑
javascript
// Vue3 响应式核心模拟
function reactive(obj) {
if (typeof obj !== 'object' || obj === null) return obj;
// 代理整个对象,而非单个属性
return new Proxy(obj, {
get(target, key, receiver) {
// Reflect 保证 this 指向正确,兼容特殊场景
const res = Reflect.get(target, key, receiver);
// 依赖收集:访问属性时才收集(懒执行)
track(target, key);
// 嵌套对象懒代理(访问时才递归,而非初始化全遍历)
return typeof res === 'object' ? reactive(res) : res;
},
set(target, key, value, receiver) {
const oldVal = Reflect.get(target, key, receiver);
const result = Reflect.set(target, key, value, receiver);
if (oldVal !== value) {
// 触发更新
trigger(target, key);
}
return result;
},
deleteProperty(target, key) {
const hadKey = Reflect.has(target, key);
const result = Reflect.deleteProperty(target, key);
if (hadKey) {
// 删除属性也触发更新
trigger(target, key);
}
return result;
}
});
}
二、核心能力差异(最直观的区别)
1. 响应式覆盖范围(Vue2 的最大痛点)
| 场景 | Vue2 | Vue3 |
|---|---|---|
| 对象新增/删除属性 | ❌ 无响应(需手动 set / delete) | ✅ 天然支持(Proxy 监听整个对象) |
| 数组下标修改/长度修改 | ❌ 无响应(如 arr[0] = 10) | ✅ 天然支持 |
| 数组方法 | ✅ 重写 7 个方法(push/pop 等) | ✅ 无需重写,Proxy 直接监听 |
| 复杂数据类型(Map/Set) | ❌ 不支持 | ✅ 完美支持 |
| 基本类型(Number/String) | ❌ 需包装成对象(如 Vue.observable) | ✅ 用 ref 包装,自动解包 |
例子:Vue2 的坑 vs Vue3 的优雅
javascript
// Vue2 场景:新增属性无响应
const vm = new Vue({
data() { return { user: { name: '张三' } } }
});
vm.user.age = 20; // 无响应,需 vm.$set(vm.user, 'age', 20)
// Vue3 场景:天然响应
const user = reactive({ name: '张三' });
user.age = 20; // 自动响应
delete user.name; // 自动响应
// 数组下标修改
const arr = reactive([1,2,3]);
arr[0] = 10; // Vue3 响应,Vue2 无响应
2. 性能差异
- Vue2:初始化时递归遍历所有属性,给每个属性加 get/set,对象层级深时初始化慢;
- Vue3:采用「懒代理」,只有访问属性时才递归处理嵌套对象,初始化性能大幅提升;且 Proxy 是浏览器原生支持的拦截器,性能优于手动定义 get/set。
3. 扩展性与调试
- Vue2:响应式逻辑和组件耦合度高,自定义响应式能力弱;
- Vue3 :响应式模块(reactivity)完全独立,可单独使用(如
import { reactive } from 'vue');且内置track/trigger机制,支持自定义依赖收集/触发逻辑,调试更友好(如 Vue Devtools 可精准追踪响应式依赖)。
三、数组处理的特殊区别
Vue2 为了让数组实现响应式,重写了数组的 7 个可变方法(push、pop、shift、unshift、splice、sort、reverse),原理是:
javascript
// Vue2 重写数组方法
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
arrayMethods[method] = function(...args) {
const result = arrayProto[method].apply(this, args);
// 触发更新
dep.notify();
return result;
};
});
而 Vue3 基于 Proxy,能直接监听数组的下标、长度、方法调用,无需重写任何数组方法,逻辑更简洁。
四、总结:核心差异表
| 特性 | Vue2 | Vue3 |
|---|---|---|
| 底层 API | Object.defineProperty | Proxy + Reflect |
| 监听粒度 | 单个属性 | 整个对象 |
| 新增/删除属性 | 需 set / delete | 天然支持 |
| 数组下标修改 | 无响应 | 有响应 |
| 复杂数据类型 | 不支持 Map/Set | 支持 Map/Set/WeakMap 等 |
| 初始化性能 | 递归全遍历,层级深则慢 | 懒递归,初始化快 |
| 响应式模块独立性 | 耦合在 Vue 实例中 | 完全独立,可单独使用 |
| 基本类型响应式 | 需包装成对象 | ref 包装,自动解包 |
五、为什么 Vue3 要换 Proxy?
- Object.defineProperty 的天然局限 :只能监听属性的 get/set,无法监听属性的新增、删除,也无法监听数组下标/长度,导致 Vue2 不得不引入 set/set/set/delete、重写数组方法等「补丁方案」;
- 性能与扩展性:Proxy 是 ES6 原生支持的对象拦截器,能监听更多操作(delete、in、遍历等),且懒递归机制大幅提升初始化性能;
- 模块化设计:响应式模块独立后,可脱离 Vue 组件使用(如在非组件场景中复用 reactive),符合 Vue3 组合式 API 的设计理念。
补充:Vue3 仍兼容 Object.defineProperty(用于 IE 兼容,但 Vue3 已放弃 IE),核心场景下完全基于 Proxy 实现,是更优的响应式方案。