Vue 3 源码:响应式系统、依赖收集和派发更新流程浅析
原文包含 Vue 3.2 及更早版本的源码片段。Vue 3.5 对响应式依赖图、computed 和 Scheduler 做过结构性调整,原文中的
dep.w、dep.n、effectTrackDepth、maxMarkerBits、旧版ReactiveEffect.parent和旧 pre 队列不能继续标注为 Vue 3.5 源码。本文保留原文“从 Proxy/Reflect 进入 track,再通过 trigger 派发到 effect、computed 和 watch”的分析主线,并把当前源码和历史实现分开。当前源码基线为 Vue Core v3.5.39。
本文内容
Proxy和Reflect的基本语义;- Vue 3.5 响应式依赖图与
track/trigger; ReactiveEffect的运行、清理、批处理和调度;- computed 的依赖传播和缓存;
watch、watchEffect、cleanup 与 flush 时机;- Vue 3.2 及更早版本 marker bits 实现的历史对照;
Reflect的 receiver 和响应式原型链专题。
一、前置知识:Proxy 和 Reflect
Vue 2 主要通过 Object.defineProperty() 转换已有属性;Vue 3 使用 Proxy 代理对象,并在代理的读取、写入、删除、判断和枚举等操作中建立或触发依赖。
Proxy 是什么?
Proxy 为对象创建代理,可以拦截属性读取、赋值、删除、in 判断、枚举和函数调用等操作。Proxy 只能代理对象,不能直接代理 string、number、null、undefined 等原始值。
const target = { count: 1 }
const proxy = new Proxy(target, {
get(target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver)
},
deleteProperty(target, key) {
return Reflect.deleteProperty(target, key)
},
has(target, key) {
return Reflect.has(target, key)
},
ownKeys(target) {
return Reflect.ownKeys(target)
},
})
Vue 3 的真实 handler 还会处理 readonly、shallow、数组方法、ref 解包、Map/Set collection handler、内建 Symbol 和响应式标记等情况,因此上面只是标准 Proxy 的最小示意。
Reflect 是什么?
Reflect 是 JavaScript 内置对象,提供与 Proxy trap 对应的标准内部操作,例如:
Reflect.get(target, key, receiver)
Reflect.set(target, key, value, receiver)
Reflect.deleteProperty(target, key)
Reflect.has(target, key)
Reflect.ownKeys(target)
Reflect 的方法通常返回明确结果:
Reflect.set()返回赋值是否成功的 boolean;Reflect.deleteProperty()返回删除是否成功的 boolean;Reflect.get()返回读取结果;- 失败时会遵循对应的标准内部方法语义。
不能笼统地说 Reflect 方法只是 Object 方法的“有返回值版本”。涉及继承 getter/setter、receiver 或失败处理时,二者的可观察行为可能不同。
receiver 为什么重要?
const obj = {
count: 22,
get count1() {
return this.count
},
}
const p = new Proxy(obj, {
get(target, key, receiver) {
// 错误的转发:getter 中的 this 会指向 target
return target[key]
},
})
const p1 = {
__proto__: p,
count: 9999,
}
console.log(p1.count1) // 22
上例中,target[key] 直接访问 obj.count1,getter 内的 this 是 obj,所以得到 22。使用 Reflect 转发:
const p = new Proxy(obj, {
get(target, key, receiver) {
return Reflect.get(target, key, receiver)
},
})
const p1 = {
__proto__: p,
count: 9999,
}
console.log(p1.count1) // 9999
Reflect.get(target, key, receiver) 会把 receiver 作为 getter 的 this。这对 Vue 保留响应式代理的 getter/setter 语义很重要,也能避免继承场景下把依赖错误地收集到原始对象。
二、reactive 和 Proxy handler
Vue 3.5 的 reactive() 入口可以概括为:
function reactive(target) {
if (isReadonly(target)) return target
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap,
)
}
真实实现还会检查:
- target 是否为对象;
- 是否已经是某种响应式代理;
- 是否命中
reactiveMap缓存; - 是否是可代理的普通对象、数组或集合;
- 是否使用了
markRaw()或不可扩展对象。
普通对象/数组使用 baseHandlers,Map、Set、WeakMap、WeakSet 使用 collection handlers。嵌套对象通常在读取时惰性地转换为 reactive,而不是一开始递归创建所有 Proxy。
当前 get 的简化流程
function get(target, key, receiver) {
if (key === ReactiveFlags.RAW) {
return target
}
const result = Reflect.get(
target,
key,
isRef(target) ? target : receiver,
)
if (!isReadonly && isTrackableKey(key)) {
track(target, TrackOpTypes.GET, key)
}
if (isShallow) return result
if (isRef(result)) {
// 数组整数索引不自动解包 ref
return isArray(target) && isIntegerKey(key)
? result
: result.value
}
return isObject(result)
? isReadonly ? readonly(result) : reactive(result)
: result
}
这段代码只用于解释顺序。当前源码还会处理内建 Symbol、数组 instrumentation、readonly、shallow 和 hasOwnProperty 等情况。具体见 baseHandlers.ts v3.5.39。
set 的关键判断
function set(target, key, value, receiver) {
const oldValue = target[key]
const hadKey = isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
if (target === toRaw(receiver) && result) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (!Object.is(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
真实 Vue 3.5 handler 还会处理 raw 值、ref 属性赋值、shallow/readonly 和数组索引。核心语义是:
- 新增 key 是
ADD; - 已有 key 且
Object.is判断值发生变化才是SET; NaN到NaN不被视为变化;Reflect.set成功并且当前 receiver 对应真正 raw target 时才触发通知;deleteProperty对成功删除的已有 key 触发DELETE;has和ownKeys也会收集相应依赖。
三、Vue 3.5 的依赖关系模型
高层模型
响应式 Proxy get/has/ownKeys
-> track(rawTarget, operationType, key)
-> 找到或创建 Dep
-> 当前 activeSub 与 Dep 建立 Link
响应式 Proxy set/delete/Map/Set 操作
-> trigger(rawTarget, operationType, key, ...)
-> 根据 SET/ADD/DELETE/CLEAR、数组 length、迭代 key 找到 Dep
-> Dep.version++、globalVersion++
-> Dep.notify() 批量通知 subscriber
-> ReactiveEffect / ComputedRefImpl 决定 scheduler 或重新计算
targetMap、Dep 和 Link
Vue 3.5.39 的核心依赖表仍然可以从外层理解为:
targetMap: WeakMap<RawTarget, Map<Key, Dep>>
但 Dep 不再是简单的 Set<ReactiveEffect>,而是维护版本号和 subscriber 链;依赖关系通过 Link 表示:
Dep <-> Link <-> Subscriber
一个 Link 同时属于:
- subscriber 的 deps / depsTail 双向链
- dep 的 subs / subsHead 双向链
这样同一条依赖边可以高效地从 source 和 subscriber 两侧清理。Link.version 记录依赖版本,Dep.version 在触发时递增。
track
export function track(target, type, key) {
if (shouldTrack && activeSub) {
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 Dep()))
dep.map = depsMap
dep.key = key
}
dep.track({ target, type, key })
}
}
开发构建会传入调试信息,生产构建会省略一部分调试对象。Dep.track() 的核心工作是:
- 查看当前
activeSub是否已经通过activeLink与这个 Dep 连接; - 没有连接时创建
Link(activeSub, dep); - 把 Link 加入 active subscriber 的 deps 链;
- 把 Link 加入 Dep 的 subscribers 链;
- 已有但在本轮被标记为过期的 Link,则同步当前 Dep 的 version。
trigger
export function trigger(target, type, key, newValue, oldValue, oldTarget) {
const depsMap = targetMap.get(target)
if (!depsMap) {
globalVersion++
return
}
startBatch()
if (type === TriggerOpTypes.CLEAR) {
depsMap.forEach(dep => dep.trigger())
} else {
run(depsMap.get(key))
if (isArray(target) && isIntegerKey(key)) {
run(depsMap.get(ARRAY_ITERATE_KEY))
}
if (type === TriggerOpTypes.ADD || type === TriggerOpTypes.DELETE) {
run(depsMap.get(ITERATE_KEY))
}
}
endBatch()
}
这是概念化代码。真实实现还会根据数组 length、Map 的 key iteration、Map SET、Set/Map CLEAR 等情况选择不同 Dep。依赖“遍历对象/数组/集合”的 effect,不仅依赖某一个具体 key,还依赖对应的迭代 key。
Dep.trigger() 会增加 dep.version 和全局 globalVersion,然后在 startBatch() / endBatch() 中通知 subscriber。它不再像旧版本那样先把多个 Dep 展平成一个 Set,再直接逐个 effect.run()。
四、ReactiveEffect:响应式副作用
创建与首次运行
effect() 会创建一个 ReactiveEffect,运行它并返回可以再次调用的 runner:
const runner = effect(() => {
console.log(state.count)
})
runner()
Vue 3.5 的 ReactiveEffect 关键字段包括:
class ReactiveEffect {
deps?: Link
depsTail?: Link
flags = EffectFlags.ACTIVE | EffectFlags.TRACKING
cleanup?: () => void
scheduler?: EffectScheduler
onStop?: () => void
}
run() 的当前流程
run() {
if (!(this.flags & ACTIVE)) {
return this.fn()
}
this.flags |= RUNNING
cleanupEffect(this) // 执行低层 onEffectCleanup 清理
prepareDeps(this) // 将已有 Link.version 暂时置为 -1
const previousSub = activeSub
const previousTracking = shouldTrack
activeSub = this
shouldTrack = true
try {
return this.fn()
} finally {
cleanupDeps(this) // 删除本轮没有重新读取的 Link
activeSub = previousSub
shouldTrack = previousTracking
this.flags &= ~RUNNING
}
}
当前实现通过 activeSub 保存和恢复嵌套上下文,通过 Link 的 version 判断动态依赖是否仍然有效:
const state = reactive({ ok: true, a: 1, b: 2 })
effect(() => {
console.log(state.ok ? state.a : state.b)
})
当 ok 从 true 变成 false,下一次 effect 运行读取 b,没有重新读取的 a 对应 Link 会在 cleanup 阶段移除。这样 effect 不会继续订阅已经不再使用的分支。
trigger() 如何触发 effect
trigger() -> dep.trigger() -> dep.notify()
-> batch subscriber
-> ReactiveEffect.trigger()
├─ PAUSED:放入 paused queue
├─ 有 scheduler:调用 scheduler
└─ 否则:runIfDirty() -> run()
因此不能把 Vue 3.5 简化为“有 scheduler 就调用 scheduler,否则无条件 effect.run()”。当前还有 dirty/version 检查、computed subscriber、批处理、暂停和递归更新 flags。
停止 effect
import { effect, stop } from 'vue'
const runner = effect(() => {
console.log(state.count)
})
stop(runner)
effect() 返回的是可以再次运行 effect 的 runner,不是 stop 函数。公共 stop(runner) 会从各个 Dep 的 subscriber 链中移除当前 effect 的 Link,清空 deps,运行 onStop 和底层 cleanup,并清除 ACTIVE 标志。
嵌套 effect 的注意点
原文中的以下写法是原生 Proxy:
const obj = new Proxy({ count: 1 }, {})
它不会接入 Vue 的 track() / trigger(),因此不能用来证明 Vue effect 的依赖行为。若要使用 Vue 响应式,应写:
const obj = reactive({ count: 1 })
effect(() => {
console.log(obj.count)
})
Vue 3.5 通过 activeSub、shouldTrack、Dep.activeLink 和 Link 的上下文恢复处理嵌套依赖,而不是原文旧代码中的 ReactiveEffect.parent 链。还要避免在外层 effect 每次运行时反复创建新的内层 effect,否则会积累多个 subscriber;需要动态管理时应使用明确的 effect scope 和 stop handle。
五、Vue 3.2 及更早版本的 marker bits(历史实现)
原文大量分析的 dep.w、dep.n、trackOpBit、effectTrackDepth 和 maxMarkerBits = 30,属于 Vue 3.2 时代的依赖清理优化。它的思路是:每轮 effect 运行前标记旧依赖,运行中标记新依赖,运行后删除“旧有但本轮未重新读取”的依赖。
简化历史流程:
run()
-> initDepMarkers(): dep.w |= trackOpBit
-> 执行 fn(),读取响应式值
-> trackEffects(): dep.n |= trackOpBit
-> finalizeDepMarkers()
dep.w 有、dep.n 无 -> 删除 effect
当嵌套深度超过位宽时,旧实现会退化到完整清理。这个模型有学习价值,但不能用于描述 Vue 3.5.39:Vue 3.5 使用 Dep.version、Link 双向链和 prepareDeps() / cleanupDeps(),不再依赖 marker bits,也没有 maxMarkerBits。
原文历史流程图(Vue 3.2 时代)
以下七张图来自原文,已下载到本地。它们用于保留文章的历史阅读线索,图中的依赖 Set、旧版 ReactiveEffect、旧 computed 或旧 Scheduler 结构不能当作 Vue 3.5.39 当前实现。







六、computed 的响应式分析
computed 的公共语义
computed 是有缓存的派生值:
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
只有读取 computed 时才会求值;依赖没有发生有效变化时,会复用缓存。也可以创建可写 computed:
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: value => {
count.value = value - 1
},
})
Vue 3.5 的 ComputedRefImpl
Vue 3.2 时代的 computed 使用独立 ReactiveEffect、_dirty、trackRefValue 和 triggerRefValue;Vue 3.5 的实现已改为 ComputedRefImpl 自身就是 subscriber:
class ComputedRefImpl implements Subscriber {
readonly dep = new Dep(this)
deps?: Link
depsTail?: Link
flags = EffectFlags.DIRTY
globalVersion = globalVersion - 1
notify() {
this.flags |= EffectFlags.DIRTY
if (!(this.flags & NOTIFIED) && activeSub !== this) {
batch(this, true)
return true
}
}
get value() {
const link = this.dep.track()
refreshComputed(this)
if (link) link.version = this.dep.version
return this._value
}
}
当前 computed 依赖流程
读取 computed.value
-> computed.dep.track()
-> refreshComputed(computed)
-> activeSub = computed
-> 执行 getter
-> getter 读取的响应式 Dep 与 computed 建立 Link
-> 恢复 activeSub,返回缓存值
当 source Dep 发生变化:
source Dep.trigger()
-> computed.notify(),设置 DIRTY
-> computed.dep.notify(),通知下游 render/effect
-> batch 结束
-> 下游 effect 根据 computed 依赖版本判断是否 dirty
-> refreshComputed,重新运行 getter
-> 只有结果确实变化时才增加 computed.dep.version
因此“computed 依赖变化就一定直接让渲染 effect 重跑”过于绝对。Vue 3.5 会结合 globalVersion、Dep version 和计算结果稳定性减少无效更新。getter 还可以接收上一次计算结果作为参数,这是较新的 API 能力。
旧 computed 源码的版本边界
下面这类代码属于 Vue 3.2/更早版本的历史实现:
this._dirty = true
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true
triggerRefValue(this)
}
})
它可以帮助理解“依赖变化使缓存失效”的抽象,但不要把 _dirty、_cacheable、triggerRefValue 和独立 ReactiveEffect 写成 Vue 3.5.39 的当前源码。
七、watch 和 watchEffect
公共 API 的区别
watch(source, callback, options)
watchEffect(effect, options)
watch显式指定 source,默认惰性执行,callback 收到 new value、old value 和 cleanup。watchEffect立即执行,并自动追踪 callback 同步执行期间读取的依赖。watch的 callback 内部读取的响应式值不会自动成为 watch source。watchEffect(async () => {})只追踪第一个await之前同步读取的依赖。
watch 支持的 source
const count = ref(0)
const state = reactive({ name: 'Vue' })
watch(count, value => {})
watch(() => state.name, value => {})
watch(state, value => {})
watch([count, () => state.name], ([newCount, newName]) => {})
不能直接把 reactive 对象的属性值传进去:
watch(state.name, callback) // ❌ 传入的是普通字符串
watch(() => state.name, callback) // ✅ 使用 getter
直接 watch 一个 reactive object 默认会深度遍历;Vue 3.5 还支持 deep: false、deep: 0 和 deep: number 控制遍历范围。深度 watch 需要遍历对象,数据量大时会产生明显成本。
getter 的简化流程
if (isRef(source)) {
getter = () => source.value
} else if (isReactive(source)) {
getter = () => reactiveGetter(source)
} else if (isFunction(source)) {
getter = cb
? () => source()
: () => runWatchEffect(source, onCleanup)
} else if (isArray(source)) {
getter = () => source.map(normalizeSource)
}
当前实现还会区分 shallow source、deep 数字深度、错误处理和多源 old value。这里只展示设计结构。
watch 的 job
const job = (immediateFirstRun = false) => {
// Vue 3.5 内部使用 EffectFlags.ACTIVE;这里省略导入和错误处理
if (
(!(effect.flags & EffectFlags.ACTIVE)) ||
(!effect.dirty && !immediateFirstRun)
) {
return
}
const newValue = effect.run()
const changed =
immediateFirstRun ||
deep ||
forceTrigger ||
hasChanged(newValue, oldValue)
if (changed) {
cleanup()
callback(newValue, oldValue, onCleanup)
oldValue = newValue
}
}
真实 Vue 3.5 代码还会处理多 source、数组 old value、once、错误处理和 cleanupMap。watch 返回的 handle 不只是一个 stop 函数,还具有:
const handle = watch(source, callback)
handle() // stop
handle.stop()
handle.pause()
handle.resume()
watchEffect 和 cleanup
watchEffect(onCleanup => {
const controller = new AbortController()
fetch(`/api/${id.value}`, {
signal: controller.signal,
})
onCleanup(() => {
controller.abort()
})
})
每次 watchEffect 即将重新运行前,Vue 会先执行上一次注册的 cleanup;停止 watcher 时也会执行 cleanup。
Vue 3.5+ 还可以使用 onWatcherCleanup():
import { onWatcherCleanup, watch } from 'vue'
watch(id, newId => {
const controller = new AbortController()
fetch(`/api/${newId}`, { signal: controller.signal })
onWatcherCleanup(() => controller.abort())
})
onWatcherCleanup() 必须在 watch callback 或 watchEffect 的同步执行阶段调用,不能在 await 之后注册。兼容写法仍然是 callback 第三个参数的 onCleanup:
watch(id, (newId, oldId, onCleanup) => {
const controller = new AbortController()
onCleanup(() => controller.abort())
})
Vue 3.5 watch 的调度模式
flush: 'sync'
source 变化时同步执行,不进入 Scheduler,也没有批处理
flush: 'pre'(默认)
首次执行可以直接运行;组件挂载后,job 进入带 PRE flag 的主队列
通常在 owner 组件 DOM 更新之前执行
flush: 'post'
进入 post-render/post-flush 队列
适合读取已经更新的 owner 组件 DOM
watch(source, callback, { flush: 'post' })
watchEffect(effect, { flush: 'pre' })
watchSyncEffect(effect)
watchPostEffect(effect)
Vue 3.5 的 runtime-core/src/apiWatch.ts 会把 watcher job 的 PRE、ALLOW_RECURSE、组件 uid 等信息交给 Scheduler;不应把旧版 queuePreFlushCb 代码当作当前实现。
八、cleanup 的两个层次
文章原文把多种 cleanup 混在了一起,实际至少要区分:
- 响应式 effect 依赖清理:Vue 3.5 的
prepareDeps()/cleanupDeps()维护 Link,删除本轮没有重新读取的依赖边。 - watcher side effect cleanup:
onCleanup/onWatcherCleanup用于取消请求、清理订阅等用户副作用。 - ReactiveEffect 的低层 cleanup:
onEffectCleanup()注册的清理函数,在 effect 下一次运行前或 stop 时执行。
它们的目的不同,不能都称为“清空 effect”。
九、Scheduler 与父子组件更新
组件 render effect 的 scheduler 会把 job 放进主队列;watcher 根据 flush 进入主队列或 post 队列。Vue 3.5 Scheduler 的主要特点:
- job 通过
QUEUEDflag 去重; - 主队列按 id 升序执行,通常父组件 id 小于子组件;
- pre watcher 使用
PREflag,并在同一组件 render job 前执行; - post callback 会去重、排序并在主队列后执行;
DISPOSEDjob 会被跳过;- 开发环境会检测超过递归限制的更新;
- 如果 flush 过程中又产生任务,会继续 drain 队列。
await nextTick()
nextTick 等待当前 flush Promise。它不等待任意 fetch、定时器或第三方 Promise。
十、target === toRaw(receiver) 与响应式原型链
Vue 的 MutableReactiveHandler.set 中有:
if (target === toRaw(receiver) && result) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
这个判断用于处理响应式对象作为另一个响应式对象原型时的标准 [[Set]] 传播,避免原型 proxy 和真正 receiver 各自触发一次更新。它不是“所有 Proxy 赋值都会触发两次”的规则。
可以用下面的结构理解场景:
const parent = reactive({ count: 1 })
const child = reactive({ name: 'child' })
Object.setPrototypeOf(child, parent)
effect(() => {
console.log(child.count)
})
// 写入可能经过 child -> parent 的原型链;Vue 只在真正 raw receiver 条件满足时触发
child.count = 2
当前 RAW 分支位于 BaseReactiveHandler.get,使用 ReactiveFlags.RAW 和 reactive map,不是把字符串 "__v_raw" 写在 createSetter() 中。toRaw() 的作用是取得原始对象,不应作为普通业务 API 频繁绕过响应式代理。
十一、文章中的旧源码问题总结
以下内容保留为历史说明,但不能继续称为 Vue 3.5.39:
dep是Set<ReactiveEffect>、effect.deps是 Dep 数组:Vue 3.5 已改为 Dep + Link 链;dep.w、dep.n、trackOpBit、maxMarkerBits:属于旧 marker bits 实现;ReactiveEffect.parent和 parent chain guard:不是 Vue 3.5 当前字段;_dirty、_cacheable、独立 computed effect:属于旧 computed 实现;trackEffects()、triggerEffects()直接遍历并运行 effect:不是当前主流程;pendingPreFlushCbs/queuePreFlushCb:属于旧 Scheduler 结构;new Proxy()的原生示例:没有 Vue track/trigger,不能证明 Vue effect 行为;effect内反复创建 effect:会积累订阅,应使用明确的 scope/stop 管理。
十二、总结
- Proxy 的读取建立依赖,写入/删除/集合操作根据操作类型通知相应 subscriber。
- Vue 3.5 的依赖关系是
targetMap -> Dep -> Link -> Subscriber,不是简单的 Set 图。 ReactiveEffect在运行前准备依赖、运行后清理过期 Link,并通过 batch/scheduler 控制重新执行。- computed 在 3.5 中自身就是 subscriber,通过 Dep version 和 globalVersion 实现惰性与稳定性优化。
watch显式追踪 source,watchEffect自动追踪同步执行期间读取的依赖。onCleanup和onWatcherCleanup用于清理过期副作用,3.5 的后者不能在 await 后注册。- Scheduler 的 pre/job/post 时序仍可作为高层模型,但队列字段和 job flags 必须按目标 Vue 版本核对。
- Reflect 的 receiver 影响 getter/setter 的 this;Vue 使用 Reflect 转发以保留标准对象语义。
官方参考
Vue Core v3.5.39 源码
- reactivity/src/effect.ts
- reactivity/src/dep.ts
- reactivity/src/reactive.ts
- reactivity/src/baseHandlers.ts
- reactivity/src/collectionHandlers.ts
- reactivity/src/computed.ts
- reactivity/src/watch.ts
- runtime-core/src/apiWatch.ts
- runtime-core/src/scheduler.ts
- runtime-core/src/renderer.ts
官方文档与标准
- Vue Reactivity in Depth
- Vue Computed
- Vue Watchers
- Vue Reactivity Core API
- MDN Proxy
- MDN Proxy get trap
- MDN Proxy set trap
- MDN Reflect.get
- MDN Reflect.set
历史源码对照
原文出处
作者:白边
原文链接:https://juejin.cn/post/7177613948907159607
来源:稀土掘金。著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。