文章目录
- 概述
- [一、 核心技术:Proxy 与 Reflect](#一、 核心技术:Proxy 与 Reflect)
-
- [1.1. Proxy 对象](#1.1. Proxy 对象)
- [1.2. Reflect 对象](#1.2. Reflect 对象)
- [1.3. Reflect结合 Proxy 的实际应用场景](#1.3. Reflect结合 Proxy 的实际应用场景)
- [二、 响应式工作流程](#二、 响应式工作流程)
-
- [3.1. 创建代理(reactive)](#3.1. 创建代理(reactive))
- [3.2. 依赖收集(Track)](#3.2. 依赖收集(Track))
- [3.3. 触发更新(Trigger)](#3.3. 触发更新(Trigger))
- [3.4. 重新执行](#3.4. 重新执行)
- [三、 Vue 2 vs Vue 3 响应式对比](#三、 Vue 2 vs Vue 3 响应式对比)
- [四、ref 的实现原理](#四、ref 的实现原理)
- 五、总结
概述
Vue 3 的响应式系统是其核心基石,它实现了"数据驱动视图"的自动更新机制。简单来说,当你的数据(Data)发生变化时,页面(View)会自动重新渲染,而你无需手动操作 DOM。
这一机制的实现主要依赖于 ES6 的 Proxy 对象 ,它彻底重构了 Vue 2 中基于 Object.defineProperty 的实现,解决了旧方案的诸多痛点。
一、 核心技术:Proxy 与 Reflect
1.1. Proxy 对象
Proxy是ES6引入的特性,用于创建一个对象的代理,可以拦截和自定义对该对象的操作。
Vue 3 不再逐个劫持对象的属性,而是通过 Proxy 直接代理整个对象。这就像给对象安装了一个"智能门卫",所有进出(读取、写入)该对象的操作都会被这个门卫拦截和监控。
Proxy 的基本语法:
bash
const proxy = new Proxy(target, handler);
- target:要代理的原始对象
- handler:包含拦截操作的配置对象
示例:
bash
const target = {
name: 'Vue',
version: 3
};
const handler = {
get(target, property, receiver) {
console.log(`读取属性: ${property}`);
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
console.log(`设置属性: ${property} = ${value}`);
return Reflect.set(target, property, value, receiver);
}
};
const proxy = new Proxy(target, handler);
// 测试
proxy.name; // 读取属性: name
proxy.version = 4; // 设置属性: version = 4
1.2. Reflect 对象
Reflect提供了操作JavaScript对象的方法,通常与Proxy配合使用。
bash
const obj = { a: 1, b: 2 };
// 传统方式
const oldVal = obj.a;
obj.a = 2;
delete obj.b;
// Reflect方式
const newVal = Reflect.get(obj, 'a');
Reflect.set(obj, 'a', 2);
Reflect.deleteProperty(obj, 'b');
为什么 Vue 需要这个?
- 传统方式的陷阱
在传统方式中,赋值操作通常总是返回赋值的值,即使操作实际上失败了,也不会报错(在非严格模式下)。 - Reflect 的严谨性
Reflect 方法通常会返回一个布尔值(true/false),明确告诉你操作是否成功。
Vue 的 Proxy 拦截器需要知道操作是否真正执行成功了,以便决定是否要触发视图更新。如果不知道成功与否,就盲目触发更新,会导致数据和视图不一致。
1.3. Reflect结合 Proxy 的实际应用场景
bash
const obj = { a: 1 };
const proxy = new Proxy(obj, {
// 拦截读取操作
get(target, key) {
console.log(`读取了 ${key}`);
// 使用 Reflect 执行默认的读取行为
// target[key] 虽然也能用,但 Reflect 更标准,且能正确处理 this 指向
return Reflect.get(target, key);
},
// 拦截设置操作
set(target, key, value) {
console.log(`设置了 ${key} 为 ${value}`);
// 执行默认的设置行为,并获取结果
const result = Reflect.set(target, key, value);
// 只有当设置成功时,才通知视图更新
if (result) {
console.log('触发视图更新');
}
return result; // 必须返回结果,告诉 Proxy 操作是否成功
}
});
二、 响应式工作流程
Vue 3 的响应式流程可以概括为一个精密的"监控-反馈"循环,包含四个核心步骤:
3.1. 创建代理(reactive)
当你调用 reactive(obj) 时,Vue 会使用 new Proxy(obj, handler) 返回一个代理对象。此时,原始对象 obj 并没有被修改,而是所有对 obj 的访问都被重定向到了这个代理上。
3.2. 依赖收集(Track)
当组件渲染(或执行 computed、watch)时,会读取响应式数据的属性。
触发机制:
Proxy的get拦截器被触发- Vue 会检查当前正在执行的"副作用函数"(例如组件的渲染函数)
- 记录下"这个属性 被 这个函数 用到了"
数据结构:
Vue 内部维护了一个类似 WeakMap<Map<Set>> 的三层数据结构来存储这种映射关系:
- 目标对象(Target)
- 属性名(Key)
- 依赖集合(Set of effects)
3.3. 触发更新(Trigger)
当数据被修改时(例如 state.count = 1):
触发机制:
Proxy的set拦截器被触发- Vue 通过内部维护的映射关系,找到所有依赖该属性的"副作用函数"
- 将它们放入待执行队列
3.4. 重新执行
Vue 会异步(微任务)批量执行这些副作用函数。对于组件来说,这就意味着重新执行渲染函数,生成新的虚拟 DOM 并更新视图。
三、 Vue 2 vs Vue 3 响应式对比
Proxy 的引入带来了革命性的变化,解决了 Vue 2 的很多"历史遗留问题"。
| 特性对比 | Vue 2 (Object.defineProperty) | Vue 3 (Proxy) |
|---|---|---|
| 监听能力 | 只能监听已存在的属性,无法监听动态新增/删除属性 | 能监听对象的所有操作,包括动态增删属性、数组索引变化 |
| 数组监听 | 无法直接监听数组变异方法(需重写 push/pop 等原型方法) |
原生支持监听数组的所有变化,无需特殊处理 |
| 性能 | 初始化时需要递归遍历对象所有属性,性能开销大 | 懒代理,只有在访问嵌套对象时才会递归创建代理,性能更优 |
| 数据结构支持 | 不支持 Map/Set | 支持 Map、Set、WeakMap、WeakSet |
四、ref 的实现原理
虽然 reactive 是基于 Proxy 的,但 Proxy 只能代理对象(Object),不能代理基本数据类型(如 string、number)。
这就是 ref 的用武之地:
实现原理
ref 内部其实是一个对象 { value: ... } ,Vue 会把这个对象用 reactive 包装起来。
使用方式
- 在模板中 :使用
ref会自动解包(不需要.value) - 在 JS 逻辑中 :操作时必须通过
.value访问或修改值
五、总结
Vue 3 的响应式原理可以简化为一句话:
利用 Proxy 劫持整个对象,在 get 时收集依赖(谁在用这个数据),在 set 时触发通知(告诉依赖者去更新)。
核心优势
- 更完整的拦截能力 :
Proxy可以监听对象的所有操作 - 更好的性能:懒代理机制,按需创建代理
- 更简洁的API :无需
Vue.set或this.$set处理动态数据 - 更广泛的支持:支持现代数据结构如 Map、Set
这种机制使得 Vue 3 的代码更加简洁、性能更强,为构建现代化的前端应用提供了坚实的基础。
本文档详细解析了 Vue 3 响应式系统的底层原理,希望能帮助您深入理解这一核心机制。