前言
Vue3 的响应式系统是整个框架的基石。与 Vue2 基于 Object.defineProperty 的实现不同,Vue3 全面采用了 ES6 的 Proxy 对象来构建响应式系统。这一转变不仅解决了 Vue2 中无法检测属性添加/删除、数组索引修改等"历史遗留问题",还带来了更好的性能表现。
本文将从源码层面,深入剖析 Vue3 响应式系统的核心实现。
为什么选择 Proxy
Object.defineProperty 存在几个无法回避的局限:
- 无法检测对象属性的新增和删除:Vue2 需要使用
Vue.set/Vue.delete来弥补 - 数组变更检测不完整:无法检测通过索引直接设置数组元素,以及修改
length属性 - 需要递归遍历对象:初始化时必须深度遍历所有属性,对深层嵌套的大对象性能开销大
- 无法拦截 Map、Set、WeakMap、WeakSet:这些集合类型的操作完全无法被追踪
而 Proxy 可以拦截 13 种操作,包括 get、set、deleteProperty、has、ownKeys 等,天然支持上述所有场景。
核心模块分析
Vue3 响应式系统的核心代码位于 packages/reactivity 目录下,主要包含以下模块:
1. reactive — 对象响应式的入口
reactive 函数通过 createReactiveObject 创建响应式对象:
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
// 非对象类型直接返回
if (!isObject(target)) {
return target
}
// 已经代理过的对象直接返回缓存
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 创建 Proxy
const proxy = new Proxy(target, baseHandlers)
proxyMap.set(target, proxy)
return proxy
}关键设计点:
- 使用
WeakMap缓存已代理对象,确保同一个原始对象始终返回同一个 Proxy 实例 - 对数组、普通对象和 Map/Set 集合类型使用不同的 Handler 策略
2. effect — 副作用追踪系统
effect 是响应式系统的核心调度器。它的工作流程可以概括为三步:
第一步:依赖收集(Track)
当 effect 内部的函数首次执行时,访问到的每一个响应式属性都会被"追踪"。具体实现通过一个全局的 activeEffect 变量,将当前正在执行的 effect 函数与所访问的属性建立映射关系。
// 简化的 track 逻辑
function track(target, type, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
dep.add(activeEffect)
}这里使用了 targetMap(WeakMap<object, Map<key, Set<effect>>>)这样的三层数据结构来维护依赖关系。
第二步:派发更新(Trigger)
当响应式数据发生变化时,Vue3 会查找所有依赖该属性的 effect 函数并重新执行。这里的调度策略非常关键——Vue3 使用 scheduler 队列来批量处理更新,避免在同一轮事件循环中重复渲染。
第三步:清理依赖
每次 effect 重新执行前,都会先清理上一次的依赖关系,确保不会出现"隐式依赖"导致的意外更新。这是通过 cleanupEffect 函数实现的。
3. ref — 原始值的响应式包装
ref 本质上是对 reactive 的补充。由于 Proxy 只能代理对象,对于原始类型(number、string、boolean 等),Vue3 通过一个带有 .value 访问器的对象来实现响应式:
class RefImpl<T> {
private _value: T
public readonly __v_isRef = true
constructor(value: T) {
this._value = toReactive(value)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
if (hasChanged(newVal, this._rawValue)) {
this._value = toReactive(newVal)
triggerRefValue(this)
}
}
}computed 的实现
computed 是 effect 的一个特殊变体。它具有两个关键特性:
- 惰性求值(Lazy Evaluation):只有访问
.value时才执行计算函数 - 缓存机制:依赖不变时直接返回缓存值,不重复执行
实现原理是通过一个 dirty 标志位来控制——当依赖变化时标记为 dirty,下次访问时重新计算并重置标志位。
总结
Vue3 的响应式系统是一个精心设计的"发布-订阅"模型:
- reactive / ref 负责将数据转换为"可观察"对象
- effect 负责注册"观察者"
- track / trigger 负责建立和维护依赖关系
- scheduler 负责调度更新,保证批量异步执行
这种设计在保持简洁 API 的同时,实现了细粒度的响应式追踪。理解其内部机制,有助于在实际项目中更好地利用 Vue3 的响应式能力,也能在遇到性能问题时做出更精准的诊断。
- 本文链接:
- 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。
