# const msg = ref('Hello World!')
在源码内部执行过程;
export function ref(value?: unknown) {
return createRef(value, false)
}
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
class RefImpl<T> {
private _value: T
private _rawValue: T // 存储原始值,用于对比
public dep?: Dep // 依赖收集容器
constructor(value: T, public readonly __v_isShallow: boolean) {
this._rawValue = value
// 如果是深度ref且值是对象,则用reactive包装
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
trackRefValue(this) // 读取时收集依赖
return this._value
}
set value(newVal) {
// 对比原始值,判断是否真的变化了
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = this.__v_isShallow ? newVal : toReactive(newVal)
triggerRefValue(this) // 触发依赖更新
}
}
}
- ref的主要作用是生成一个带有value的getter和setter属性的RefImpl实例
- 当在使用msg的地方才会触发getter函数进行【依赖收集】,在更改msg的值的时候才会触发更新
Dep类
/**
* @internal
*/
export class Dep {
//依赖点自身的“变更版本号”。每次 `trigger()` 都会 `version++`,用于 computed 的快速判定/缓存失效。
version = 0
/**
* Link between this dep and the current active effect
*当前正在运行的 effect(`activeSub`)** 与这个 `Dep` 之间的那条 `Link`(缓存用)。同一个 effect 在一次执行里多次访问同一属性时,避免重复创建/重复订阅
*/
activeLink?: Link = undefined
/**
* Doubly linked list representing the subscribing effects (tail)
订阅者链表的**尾节点**(tail)。每个 `Link` 对应一个订阅者(effect 或 computed)
*/
subs?: Link = undefined
/**
* Doubly linked list representing the subscribing effects (head)
* DEV only, for invoking onTrigger hooks in correct order
订阅者链表的**头节点**(head)
*/
subsHead?: Link
/**
* For object property deps cleanup
这个 `Dep` 属于哪个 `depsMap`(`targetMap.get(target)`)以及对应的属性 `key` 是谁。主要用于后续清理/调试/定位(比如清掉某个对象某个 key 的 dep)
*/
map?: KeyToDepMap = undefined
key?: unknown = undefined
/**
* Subscriber counter
订阅者计数器。`addSub()` 时会 `dep.sc++`,用于统计/优化(例如判断是否需要做某些懒订阅逻辑)
*/
sc: number = 0
/**
* @internal
*/
readonly __v_skip = true
// TODO isolatedDeclarations ReactiveFlags.SKIP
constructor(public computed?: ComputedRefImpl | undefined) {
if (__DEV__) {
this.subsHead = undefined
}
}
track(debugInfo?: DebuggerEventExtraInfo): Link | undefined {
//activeSub当前的活动的effect
if (!activeSub || !shouldTrack || activeSub === this.computed) {
return
}
//这里的activeLink用于优化同一个值在同一个effect里被多次访问;即当同一个值在被重复访问只会创建一个Link,会直接返回。
let link = this.activeLink
if (link === undefined || link.sub !== activeSub) {
// 1. 创建 Link 节点(核心桥梁)
link = this.activeLink = new Link(activeSub, this)
// add the link to the activeEffect as a dep (as tail)
if (!activeSub.deps) {
activeSub.deps = activeSub.depsTail = link
} else {
link.prevDep = activeSub.depsTail
activeSub.depsTail!.nextDep = link
activeSub.depsTail = link
}
addSub(link)
} else if (link.version === -1) {
// reused from last run - already a sub, just sync version
link.version = this.version
// If this dep has a next, it means it's not at the tail - move it to the
// tail. This ensures the effect's dep list is in the order they are
// accessed during evaluation.
if (link.nextDep) {
const next = link.nextDep
next.prevDep = link.prevDep
if (link.prevDep) {
link.prevDep.nextDep = next
}
link.prevDep = activeSub.depsTail
link.nextDep = undefined
activeSub.depsTail!.nextDep = link
activeSub.depsTail = link
// this was the head - point to the new head
if (activeSub.deps === link) {
activeSub.deps = next
}
}
}
if (__DEV__ && activeSub.onTrack) {
activeSub.onTrack(
extend(
{
effect: activeSub,
},
debugInfo,
),
)
}
return link
}
trigger(debugInfo?: DebuggerEventExtraInfo): void {
this.version++
globalVersion++
this.notify(debugInfo)
}
notify(debugInfo?: DebuggerEventExtraInfo): void {
startBatch()
try {
if (__DEV__) {
// subs are notified and batched in reverse-order and then invoked in
// original order at the end of the batch, but onTrigger hooks should
// be invoked in original order here.
for (let head = this.subsHead; head; head = head.nextSub) {
if (head.sub.onTrigger && !(head.sub.flags & EffectFlags.NOTIFIED)) {
head.sub.onTrigger(
extend(
{
effect: head.sub,
},
debugInfo,
),
)
}
}
}
for (let link = this.subs; link; link = link.prevSub) {
if (link.sub.notify()) {
// if notify() returns `true`, this is a computed. Also call notify
// on its dep - it's called here instead of inside computed's notify
// in order to reduce call stack depth.
;(link.sub as ComputedRefImpl).dep.notify()
}
}
} finally {
endBatch()
}
}
}
-
track()(依赖收集):-
通过
activeLink判断“当前 effect 是否已经和我建立过 Link”,没有就new Link(activeSub, this),并把它挂到:- effect 的 dep 链表(
activeSub.deps/depsTail)上:方便 effect 下次运行前把旧依赖标记/清理。 - dep 的 subs 链表(
subs/subsHead)上:方便将来触发更新时通知订阅者。
- effect 的 dep 链表(
-
link.version === -1分支:说明这个 Link 是“上一次运行留下的旧 Link,被复用”,这里会把它的version同步回当前dep.version,并把它移动到 effect 的 dep 链表尾部,确保依赖顺序与访问顺序一致(便于清理/调试)。
-
-
trigger()/notify()(派发更新):-
trigger()先做this.version++,再做globalVersion++,然后notify()。 -
notify()遍历subs链表,逐个link.sub.notify()让订阅者进入调度/执行。 -
如果订阅者是 computed(
notify()返回true),会额外触发它自己的dep.notify(),用于把“依赖了该 computed 的外层 effect”也一起通知到(减少调用栈深度的优化点)。
- 读取响应式属性时:
track(target, key)找到/创建对应Dep,然后dep.track()把“当前 effect”订阅进来(靠activeLink/subs等结构)。
- 修改响应式属性时:
trigger(target, key)找到对应Dep,dep.trigger()增加版本并notify(),遍历subs去通知所有订阅者更新。
-
总结:
Vue 3 的 Dep 实现体现了精巧而高效的设计:
-
本质是增强的 Set:
Dep = Set<ReactiveEffect> & TrackedMarkers -
双向链接:dep ↔ effect 互相引用,便于清理和调试
-
两种使用模式:
- ref:每个实例有自己的
dep属性 - reactive:全局
targetMap中懒创建属性级dep
- ref:每个实例有自己的
-
性能优化:
- 二进制位标记避免重复收集
- Set 的 O(1) 操作
- 懒创建 + WeakMap 自动 GC
-
分离关注点:dep 只管理依赖集合,不涉及值比较或副作用调度
Link:依赖链接(Link)的核心数据结构,实现了双向链表来高效管理依赖关系
export class Link {
/**
* - Before each effect run, all previous dep links' version are reset to -1
* - During the run, a link's version is synced with the source dep on access
* - After the run, links with version -1 (that were never used) are cleaned
* up
*/
version: number//
版本号,用于依赖清理
/**
* Pointers for doubly-linked lists
*/
nextDep?: Link//下一个依赖
prevDep?: Link//前一个依赖
nextSub?: Link//下一个订阅者
prevSub?: Link//前一个订阅者
prevActiveLink?: Link 前一个活跃链接
constructor(
public sub: Subscriber,
public dep: Dep,
) {
this.version = dep.version
this.nextDep =
this.prevDep =
this.nextSub =
this.prevSub =
this.prevActiveLink =
undefined
}
}
五个指针的作用:
| 指针 | 方向 | 用途 |
|---|---|---|
nextDep / prevDep | 水平方向 | 连接同一个 effect 的所有依赖 |
nextSub / prevSub | 垂直方向 | 连接同一个依赖 的所有订阅者 |
prevActiveLink | 特殊 | 用于 effect 执行期间的上下文管理 |
Link 在响应式系统中的实际应用
双向链表的优势在 Vue 中的体现
1. O(1) 的清理操作
typescript
// 清理 effect 的所有依赖
function cleanupEffect(effect: ReactiveEffect) {
let link = effect.deps // 从依赖链表头部开始
while (link) {
// 从 dep 的订阅链表中移除这个 link
removeSubFromDep(link) // O(1) 操作!
link = link.nextDep // 继续下一个依赖
}
// 清空 effect 的依赖链表
effect.deps = effect.depsTail = undefined
}
2. 保持访问顺序
typescript
effect(() => {
console.log(a.value) // 先访问
console.log(b.value) // 后访问
console.log(c.value) // 最后访问
})
// effect.deps 链表顺序:a → b → c
// 这个顺序在调试和优化时非常有用
3. 高效的重复访问处理
typescript
effect(() => {
// 同一个 effect 内多次访问同一个 dep
const x = a.value * 2
const y = a.value * 3 // 第二次访问
// Link 被重用,只更新 version
// 不会创建新的 Link 节点
})
4. 计算属性的级联更新
typescript
const a = ref(1)
const computedA = computed(() => a.value * 2)
const computedB = computed(() => computedA.value * 3)
effect(() => {
console.log(computedB.value)
})
// 更新链:a → computedA → computedB → effect
// 双向链表使得这个级联通知更高效
⚡ 性能对比:链表 vs Set
| 操作 | Set 实现 | 双向链表实现 | 性能提升原因 |
|---|---|---|---|
| 添加订阅 | Set.add(effect) | 链表尾部插入 | 相当 |
| 移除订阅 | Set.delete(effect) | 链表节点移除 | 链表直接操作指针更快 |
| 清理 effect | O(m×n) 嵌套循环 | O(n) 单次遍历 | 链表双向链接直接访问 |
| 内存占用 | 每个 Set 额外开销 | 每个 Link 固定大小 | 链表更紧凑 |
| GC 压力 | 频繁创建 Set | Link 对象重用 | 减少内存分配 |
# 高效重复访问机制详解
什么是"重复访问"?
基本场景
typescript
const count = ref(0)
effect(() => {
// 同一个 effect 函数内多次访问 count.value
const double = count.value * 2 // 第一次访问
const triple = count.value * 3 // 第二次访问(重复访问!)
const quadruple = count.value * 4 // 第三次访问(再次重复!)
console.log(double, triple, quadruple)
})
更常见的实际场景
typescript
effect(() => {
// 在条件判断、循环中重复访问
if (user.value.age > 18) {
console.log('Adult:', user.value.name) // 第1次
}
for (let i = 0; i < user.value.friends.length; i++) { // 第2次
console.log('Friend:', user.value.friends[i].name) // 循环中多次访问
}
return user.value.score * 1.1 // 第N次
})
⚡ 性能问题:重复访问的代价
Vue 3.3(Set 实现)的问题
typescript
// 伪代码:每次访问都执行完整的依赖收集
function trackRefValue(ref) {
if (!activeEffect) return
let dep = ref.dep
if (!dep) {
dep = ref.dep = new Set()
}
dep.add(activeEffect) // ⚠️ 每次都要添加!
activeEffect.deps.push(dep) // ⚠️ 每次都要添加!
}
// 结果:一个 effect 对同一个 ref 产生多个相同的依赖记录
// dep = Set { effect, effect, effect, ... } // 重复!
// effect.deps = [dep, dep, dep, ...] // 重复!
问题:
- Set 中重复存储:同一个 effect 被多次添加
- 数组重复引用:effect.deps 中有多个相同的 dep 引用
- 清理时重复工作:需要多次从同一个 dep 中移除同一个 effect
- 触发时重复执行:同一个 effect 可能被触发多次
🚀 Vue 3.4+ 的优化方案
核心机制:Link 节点的重用和版本号标记
1. activeLink 快速查找机制
class Dep { // 🔑 关键:记录最后一个链接到当前 effect 的 Link activeLink?: Link = undefined
track() { if (!activeEffect) return
// 1. 先检查是否有可重用的 Link
let link = this.activeLink
// 2. 检查是否可重用:同一个 effect 且未失效
if (link === undefined || link.sub !== activeEffect) {
// 需要创建新 Link
link = this.activeLink = new Link(activeEffect, this)
// ... 建立双向链接
} else if (link.version === -1) {
// 🔥 可重用:更新版本号,移动节点到尾部
link.version = this.version
// 移动到 effect 依赖链表的尾部(保持访问顺序)
if (link.nextDep) {
// 链表重排操作...
}
}
// else: link.version === this.version,说明已是最新,什么都不做!
return link
}}
2. version 版本号系统
typescript
class Dep {
version = 0 // dep 的版本号,每次 trigger 时递增
trigger() {
this.version++ // 🔥 值变化时版本号+1
globalVersion++
this.notify()
}
}
class Link {
version = -1 // 初始值 -1 表示"未收集"或"需要更新"
// 在 track() 中:
if (link.version === -1) {
// 需要更新:将 link.version 设置为当前 dep.version
link.version = dep.version
} else if (link.version === dep.version) {
// 已是最新:什么都不做!
return link
}
}