在 Vue 3 的响应式系统中,reactive 是构建深层响应式对象的核心 API。其背后依赖于 ES6 的 Proxy 特性,通过拦截对象的基本操作来建立响应式链条。要真正掌握 reactive,必须深入理解它究竟拦截了哪些操作,以及这些拦截如何协同工作以实现自动依赖追踪和更新触发。本节将从源码层面剖析 reactive 的本质,详细解析 get、set、has、ownKeys 等拦截器的作用机制。
一、Proxy 的核心作用:操作拦截
reactive 的实现基于 Proxy。当你调用 reactive(obj) 时,Vue 会返回一个 Proxy 实例,该实例代理了原始对象 obj。Proxy 可以拦截对目标对象的各种操作,如属性读取、赋值、枚举等。
reactive 使用的 Proxy handler 定义了多个拦截器,每个拦截器负责处理特定的操作,并在适当时机调用 track(依赖收集)或 trigger(触发更新)。
二、get 拦截器:依赖收集的起点
get 拦截器是响应式系统中最关键的一环,它负责在属性读取时进行依赖收集。
const get = (target, key, receiver) => {
// 1. 收集依赖
track(target, key);
// 2. 获取原始值
const result = Reflect.get(target, key, receiver);
// 3. 深层响应式:如果值是对象,递归 reactive
if (isObject(result)) {
return reactive(result);
}
// 4. 返回结果
return result;
};详细解析:
track(target, key): 这是依赖收集的核心。track函数会检查当前是否有正在运行的副作用(如组件渲染、watchEffect)。如果有,它会将该副作用记录为target对象上key属性的依赖。这样,当key被修改时,Vue 就知道需要重新执行哪些副作用。Reflect.get: 使用Reflect.get安全地获取目标对象的属性值。receiver参数确保this绑定正确,特别是在访问 getter 属性时。深层响应式: 如果获取的值是一个对象(或数组),
reactive会递归地将其转换为响应式对象。这是reactive实现“深层响应式”的关键。它确保了无论数据嵌套多深,修改任何层级的属性都能被追踪。返回值: 最终返回经过响应式处理的值。对于原始值(number、string 等),直接返回;对于对象,则返回其
reactive代理。
三、set 拦截器:更新触发的枢纽
set 拦截器负责在属性被修改时触发更新。
const set = (target, key, value, receiver) => {
// 1. 获取旧值
const oldValue = target[key];
// 2. 设置新值
const result = Reflect.set(target, key, value, receiver);
// 3. 检查值是否真正改变
if (!Object.is(value, oldValue)) {
// 4. 触发依赖更新
trigger(target, key);
}
// 5. 返回结果
return result;
};详细解析:
获取旧值: 在设置新值前,先读取旧值,用于后续的变更检测。
Reflect.set: 安全地设置属性值。与get一样,receiver确保this正确绑定。变更检测: 使用
Object.is比较新旧值。Object.is能正确处理NaN和+0/-0的比较,比===更精确。只有当值真正改变时,才触发更新,避免不必要的渲染。trigger(target, key): 这是更新触发的核心。trigger函数会查找target对象上key属性的所有依赖(即之前通过track收集的副作用),并将它们加入调度队列,等待执行。
四、has 拦截器:in 操作符的响应式支持
has 拦截器用于拦截 in 操作符和 with 语句中的属性检查。
const has = (target, key) => {
// 收集依赖:检查属性是否存在也是一种依赖
track(target, key);
// 返回属性是否存在
return Reflect.has(target, key);
};作用场景:
const state = reactive({ count: 0 });
// 模板中使用 v-if="count in state"
if ('count' in state) {
// 会触发 has 拦截器,收集依赖
}当 state.count 被添加或删除时,依赖于 'count' in state 的副作用需要重新执行。has 拦截器通过 track 收集这些依赖,确保 in 操作的响应式。
五、ownKeys 拦截器:键枚举的响应式
ownKeys 拦截器用于拦截 Object.keys、Object.getOwnPropertyNames、Object.getOwnPropertySymbols、for...in 循环等操作。
const ownKeys = (target) => {
// 收集依赖:枚举对象的所有键也是一种依赖
track(target, ITERATE_KEY);
// 返回所有自有属性键
return Reflect.ownKeys(target);
};详细解析:
ITERATE_KEY: 这是一个特殊的 key(通常为Symbol('iterate'))。当枚举操作发生时,track收集的依赖是针对ITERATE_KEY的。这样,当对象的任何键被添加或删除时,trigger可以通过ITERATE_KEY触发所有依赖于枚举操作的副作用。触发场景:
js// 以下操作都会触发 ownKeys Object.keys(state); for (let key in state) { /* ... */ }如果
state后续通过delete state.count或state.newKey = 1修改,依赖于Object.keys(state)的计算属性或模板需要重新执行。
六、其他重要拦截器
除了上述四个,reactive 还实现了其他拦截器以确保完整性和正确性。
1. deleteProperty
拦截 delete 操作符。
const deleteProperty = (target, key) => {
const result = Reflect.deleteProperty(target, key);
if (result) {
// 属性删除成功,触发更新
trigger(target, key, 'delete');
}
return result;
};2. defineProperty
拦截 Object.defineProperty。
const defineProperty = (target, key, descriptor) => {
const result = Reflect.defineProperty(target, key, descriptor);
if (result) {
// 属性描述符改变,触发更新
trigger(target, key);
}
return result;
};3. getOwnPropertyDescriptor
拦截 Object.getOwnPropertyDescriptor。
const getOwnPropertyDescriptor = (target, key) => {
// 访问属性描述符也是一种依赖
track(target, key);
return Reflect.getOwnPropertyDescriptor(target, key);
};七、响应式链条的构建过程
当一个对象被 reactive 包装后,完整的响应式链条如下:
读取属性(
state.count):- 触发
get拦截器。 - 执行
track(state, 'count'),收集当前副作用作为依赖。 - 返回值(若为对象,递归
reactive)。
- 触发
修改属性(
state.count = 1):- 触发
set拦截器。 - 比较新旧值,若不同则执行
trigger(state, 'count')。 trigger查找state上count的所有依赖,并调度执行。
- 触发
枚举属性(
for (let key in state)):- 触发
ownKeys拦截器。 - 执行
track(state, ITERATE_KEY),收集依赖。 - 当
state的键集合改变时,通过ITERATE_KEY触发更新。
- 触发
检查属性(
'count' in state):- 触发
has拦截器。 - 执行
track(state, 'count'),收集依赖。 - 当
count属性被添加或删除时,触发更新。
- 触发
八、设计哲学:透明的代理
reactive 的设计目标是“透明”。它通过 Proxy 拦截所有可能改变或读取对象状态的操作,使得响应式行为对使用者透明。开发者可以像操作普通对象一样操作 reactive 对象,而 Vue 在背后自动建立依赖关系。
这种设计的优势是开发体验极佳,但代价是:
- 无法代理
Map、Set、WeakMap、WeakSet等集合类型(它们有自己的一套方法,需特殊处理)。 - 对某些边缘操作(如
__proto__、caller)的处理需要额外注意。
九、总结
reactive 的本质是通过 Proxy 拦截对象的底层操作,构建一个完整的响应式链条:
get:读取属性时收集依赖。set:设置属性时触发更新(仅当值改变)。has:in操作时收集依赖。ownKeys:枚举操作时收集依赖(使用ITERATE_KEY)。
这些拦截器协同工作,确保了 Vue 能够精确地追踪数据依赖,并在数据变化时高效地触发视图更新。理解这些底层机制,是掌握 Vue 3 响应式系统精髓的关键。