Vue 3 响应式原理源码全解析:从 Proxy 到 computed/watch 的完整实现
一、写在开头
前端框架的核心任务是将数据映射为 DOM,并在数据变化时自动更新视图。响应式系统正是实现这一自动更新的基础——当应用状态改变时,依赖该状态的视图、计算属性、侦听器等能够自动重新执行。
Vue 2 使用Object.defineProperty劫持对象属性的 getter/setter,配合观察者模式实现了响应式。但它存在几个难以逾越的局限:无法检测属性的添加或删除;对数组的变异方法需要额外处理;初始化时需递归遍历所有属性,存在性能开销。
Vue 3 基于 ES2015 的Proxy实现了全新的响应式系统,不仅完美解决了上述问题,还带来了更好的性能、更简洁的代码结构,同时支持 Map、Set 等复杂数据类型。本文将结合packages/reactivity目录下的源码,深度剖析这一系统的设计与实现,并手写一个 mini 版巩固理解。
二、响应式系统的核心:Proxy vs Object.defineProperty
2.1 Object.defineProperty 的工作原理
Object.defineProperty(obj, key, descriptor)可以精确控制对象属性的读取和赋值行为:
javascript
let obj = {} let val = 1 Object.defineProperty(obj, 'a', { get() { console.log('get a') return val }, set(newVal) { console.log('set a') val = newVal } }) obj.a // 触发 get obj.a = 2 // 触发 setVue 2 通过递归遍历对象的所有属性,为每个属性添加 getter 和 setter。在 getter 中收集依赖(Watcher),setter 中触发更新。但这种方式存在明显局限:
无法监听属性新增/删除:
obj.newKey = 'value'不会触发 setter,因此 Vue 2 提供了Vue.set/delete。无法直接监听数组索引和 length 变化:需要重写数组的 7 种变异方法(push/pop/shift/unshift/splice/sort/reverse)。
初始化时需要深度递归,影响启动性能,且对于深层嵌套的对象可能造成不必要的观察。
简单模拟 Vue 2 响应式:
javascript
function defineReactive(obj, key) { let val = obj[key] const dep = [] // 依赖列表(简化) Object.defineProperty(obj, key, { get() { if (currentEffect) dep.push(currentEffect) return val }, set(newVal) { val = newVal dep.forEach(fn => fn()) } }) } // 需要递归遍历 function observe(obj) { Object.keys(obj).forEach(key => defineReactive(obj, key)) }2.2 Proxy 的工作原理
Proxy可以创建一个对象的代理,拦截几乎所有基本操作:
javascript
const p = new Proxy(target, { get(target, key, receiver) { /* ... */ }, set(target, key, value, receiver) { /* ... */ }, deleteProperty(target, key) { /* ... */ }, // 共支持13种拦截方法 })Vue 3 利用 Proxy 的get、set、deleteProperty、has、ownKeys等拦截器,能够全面捕获对数据的操作。其优势包括:
可监听属性的增删:
set拦截器可感知新增属性,deleteProperty拦截删除操作。自然支持数组:直接拦截
arr[0]、arr.length等操作,无需重写数组方法。懒代理:只在读取某个属性且其值是对象时才进行递归代理,提升初始化性能。
可代理更多数据类型:Map、Set、WeakMap、WeakSet 等。
简单 Proxy 示例:
javascript
const handler = { get(target, key) { if (typeof target[key] === 'object' && target[key] !== null) { return new Proxy(target[key], handler) // 懒代理 } return target[key] }, set(target, key, value) { target[key] = value console.log('更新视图') return true } } const data = new Proxy({ a: 1, arr: [1,2,3] }, handler) data.a = 2 // 触发更新 data.b = 3 // 触发更新(新增属性) data.arr.push(4) // 触发更新2.3 两者的详细对比
| 对比维度 | Object.defineProperty | Proxy |
|---|---|---|
| 功能完整性 | 无法监听属性增删、数组索引和 length | 可拦截13种操作,覆盖所有数据变更场景 |
| 性能 | 初始化需递归遍历,内存消耗大 | 懒代理,按需递归,整体更高效 |
| 易用性 | 需要额外 API(set/set/delete),数组特殊处理 | 统一拦截,代码简洁 |
| 兼容性 | 支持 IE9+,生态成熟 | 不支持 IE,但现代浏览器全面支持 |
| 数据结构支持 | 仅限普通对象 | 支持数组、Map、Set、WeakMap、WeakSet |
Vue 3 选择 Proxy 正是因为它提供了更完整的拦截能力和更友好的开发体验。虽然放弃了 IE 兼容性,但顺应了现代前端发展趋势,也使得响应式系统的实现代码量大幅减少。
三、Vue 3 响应式系统的整体架构
Vue 3 响应式系统的核心模块均位于packages/reactivity/src目录下,主要包括:
reactive:将普通对象转换为响应式代理
ref:包装基本类型值(或对象),使其变为响应式
effect:创建副作用函数,并在其依赖变化时自动重新执行
computed:基于其他响应式数据计算得出新值,具有懒执行和缓存特性
watch/watchEffect:侦听响应式数据的变化并执行回调
整体工作流程如下:
text
[应用数据] │ ├─ reactive(obj) / ref(val) 创建响应式数据 │ └─> effect / computed / watch 创建副作用,依赖响应式数据 │ ▼ Proxy get 拦截 ──── track(收集依赖)────> 存储到 targetMap (WeakMap) │ Proxy set/delete 拦截 ─── trigger(触发依赖)────> 取出对应 effect 重新执行 │ ▼ [异步更新队列] -> 执行副作用 -> 更新 DOM
核心概念区分:
响应式数据:被 Proxy 代理后的对象或通过 ref 包装的值,能够被“追踪”变化。
副作用函数(effect):依赖响应式数据的函数,当数据变化时会被重新调用。组件的渲染函数就是典型的副作用。
整个系统围绕“依赖收集”和“触发更新”两个阶段运转,接下来我们将深入源码逐一剖析。
四、依赖收集的完整实现
4.1 依赖收集的基本原理
依赖是指某个副作用函数读取了哪些响应式数据。当数据变化时,需要通知这些副作用重新执行。依赖收集发生在响应式数据的get 拦截阶段。
Vue 3 使用一个三层结构来存储依赖关系:
text
targetMap: WeakMap<target, depsMap> depsMap: Map<key, dep> dep: Set<ReactiveEffect>
targetMap是一个 WeakMap,键为原始目标对象,值为depsMap。使用 WeakMap 利于垃圾回收。depsMap是一个 Map,键为目标对象的具体属性名,值为dep。dep是一个 Set,存放所有与该属性关联的ReactiveEffect(副作用对象)。Set 确保依赖不重复。
4.2 核心源码分析
reactive 函数(packages/reactivity/src/reactive.ts)
javascript
export function reactive(target) { // 如果目标已是只读代理或响应式代理,直接返回 if (isReadonly(target)) return target return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap) }createReactiveObject核心逻辑:
javascript
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) { // 非对象类型直接返回 if (!isObject(target)) return target // 如果已经代理过,返回缓存的代理(proxyMap 防止重复代理) const existingProxy = proxyMap.get(target) if (existingProxy) return existingProxy // 根据类型选择处理器:普通对象用 baseHandlers,集合类型用 collectionHandlers const proxy = new Proxy(target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers) proxyMap.set(target, proxy) return proxy }Proxy 的get 拦截器实现在baseHandlers中(packages/reactivity/src/baseHandlers.ts),核心是调用track收集依赖:
javascript
function createGetter(isReadonly = false, shallow = false) { return function get(target, key, receiver) { // 处理各种内部 key(如 __v_isReactive、__v_raw 等)... const res = Reflect.get(target, key, receiver) // 非只读时进行依赖收集 if (!isReadonly) { track(target, TrackOpTypes.GET, key) } // 若值是对象且非浅层代理,递归转换为响应式(懒代理) if (isObject(res)) { return isReadonly ? readonly(res) : reactive(res) } return res } }track 函数详细解析(packages/reactivity/src/effect.ts)
javascript
export function track(target, type, key) { // 当前激活的 effect 存在时才收集 if (shouldTrack && activeEffect) { let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (!dep) { depsMap.set(key, (dep = createDep())) } // 将当前 effect 加入依赖集合 trackEffects(dep) } } function trackEffects(dep) { // 避免重复收集:判断 effect 的 deps 数组是否已包含此 dep if (!dep.has(activeEffect)) { dep.add(activeEffect) activeEffect.deps.push(dep) // effect 也反向记录该 dep,便于清理 } }优化策略:
activeEffect确保只有当前正在执行的副作用才会被收集,避免无关读取触发收集。双向记录(effect 记录它依赖的所有 dep,dep 记录所有订阅的 effect),使得 effect 重新执行时可以清理过期的依赖。
使用位运算
trackOpBit进行递归跟踪的控制,防止深层嵌套下的重复收集。
4.3 ref 的特殊处理
ref 用于让基本类型值(或对象)具备响应式。对象类型 ref 内部会调用reactive转换.value的值。
ref 的实现(packages/reactivity/src/ref.ts)
javascript
export function ref(value) { return createRef(value, false) } function createRef(rawValue, shallow) { if (isRef(rawValue)) return rawValue return new RefImpl(rawValue, shallow) } class RefImpl { constructor(value, __v_isShallow) { this._rawValue = value this._value = __v_isShallow ? value : toReactive(value) // 对象转 reactive this.__v_isRef = true } get value() { trackRefValue(this) // 收集依赖 return this._value } set value(newVal) { if (hasChanged(this._rawValue, newVal)) { this._rawValue = newVal this._value = toReactive(newVal) triggerRefValue(this) // 触发更新 } } }ref 依赖收集直接通过trackRefValue将自身作为一个整体收集,而不是按属性。其依赖存储在dep属性(Set)中。与 reactive 相比,ref 更轻量,适合单一值的响应式包裹,且可通过.value直接替换整个对象。
五、触发更新的完整实现
5.1 触发更新的基本原理
触发更新发生在响应式数据的set 或 deleteProperty 拦截阶段。当数据变更时,根据修改类型(新增、修改、删除)从targetMap中找到对应的 dep,并执行所有关联的副作用。
为提升性能,Vue 3 不会立即同步执行副作用,而是将副作用放入一个异步更新队列,在下一个 tick 统一执行,从而批量处理更新,避免不必要的重复计算。
5.2 核心源码分析
set 拦截器(packages/reactivity/src/baseHandlers.ts)
javascript
function createSetter(shallow = false) { return function set(target, key, value, receiver) { const oldValue = target[key] // 判断操作类型:新增(ADD)还是修改(SET) const hadKey = hasOwn(target, key) const result = Reflect.set(target, key, value, receiver) // 若 receiver 是 target 的代理且未被屏蔽,触发更新 if (target === toRaw(receiver)) { if (!hadKey) { trigger(target, TriggerOpTypes.ADD, key, value) } else if (hasChanged(value, oldValue)) { trigger(target, TriggerOpTypes.SET, key, value, oldValue) } } return result } }trigger 函数详细解析(packages/reactivity/src/effect.ts)
javascript
export function trigger(target, type, key, newValue, oldValue) { const depsMap = targetMap.get(target) if (!depsMap) return let deps = [] // 获取与 key 直接相关的依赖 if (key !== undefined) { deps.push(depsMap.get(key)) } // 处理 ADD / DELETE / SET 等特殊操作(如数组 length 变更、迭代器相关) switch (type) { case TriggerOpTypes.ADD: // 对数组新增,需要触发 length 相关的依赖 if (isArray(target)) { deps.push(depsMap.get('length')) } // ... break case TriggerOpTypes.DELETE: // ... break case TriggerOpTypes.SET: // ... break } // 合并所有需要执行的 effect const effects = [] for (const dep of deps) { if (dep) { effects.push(...dep) } } // 通过调度器执行副作用(加入更新队列) triggerEffects(createDep(effects)) } function triggerEffects(dep) { for (const effect of dep) { // 若 effect 有 scheduler,则使用 scheduler 调度执行 if (effect.scheduler) { effect.scheduler() } else { effect.run() } } }不同操作类型的处理:新增属性除了触发该属性的依赖,还会触发与迭代(如Object.keys)相关的依赖;删除属性同理;对于数组,修改length或通过索引设置可能触发多个依赖。
5.3 异步更新队列
当副作用被触发时,不会立即执行effect.run(),而是推入一个队列queue,并交由scheduler调度。这样同一个 Tick 内的多次数据变更只会触发一次副作用刷新。
nextTick的实现(packages/runtime-core/src/scheduler.ts)
javascript
const queue = [] let isFlushing = false const resolvedPromise = Promise.resolve() export function queueJob(job) { if (!queue.includes(job)) { queue.push(job) queueFlush() } } function queueFlush() { if (!isFlushing) { isFlushing = true resolvedPromise.then(flushJobs) } } function flushJobs() { try { for (let i = 0; i < queue.length; i++) { const job = queue[i] job() } } finally { isFlushing = false queue.length = 0 } }nextTick实际上就是Promise.resolve().then(callback)(在 Vue 3 中优先使用微任务),等待当前宏任务执行完毕后清空队列。
批量更新的优化:
同一个 effect 若被多次触发,只会入队一次(通过
queue.includes判重)。允许提供
scheduler,例如组件的渲染函数会使用自定义 scheduler,确保渲染过程高效批处理。
六、computed 的实现原理
6.1 computed 的基本概念和特性
计算属性接收一个 getter 函数,返回一个只读的响应式ref对象。它具有两大核心特性:
懒执行:只有在被读取时才会计算值,若没有依赖项变化,不会主动重新计算。
缓存:依赖项不变时多次读取会立即返回上一次的计算结果,而不会重复执行 getter。
可写计算属性:若提供 setter,则可通过赋值触发展更新。
6.2 核心源码分析
computed 函数(packages/reactivity/src/computed.ts)
javascript
export function computed(getterOrOptions) { let getter, setter if (isFunction(getterOrOptions)) { getter = getterOrOptions setter = () => warn('Write operation failed: computed value is readonly') } else { getter = getterOrOptions.get setter = getterOrOptions.set } return new ComputedRefImpl(getter, setter, isFunction(getterOrOptions) || !getterOrOptions.set) }ComputedRefImpl 类
javascript
class ComputedRefImpl { constructor(getter, _setter, isReadonly) { this._setter = _setter this._dirty = true // 脏标记,控制是否需要重新计算 this._cacheable = true // 是否启用缓存 this.effect = new ReactiveEffect(getter, () => { // 当依赖变化时,不立即求值,仅标记为脏,并触发依赖此 computed 的 effect 更新 if (!this._dirty) { this._dirty = true triggerRefValue(this) // 通知上层订阅者 } }) } get value() { // 读取时进行依赖收集 const self = toRaw(this) trackRefValue(self) // 如果脏或不需要缓存,则重新计算 if (self._dirty || !self._cacheable) { self._dirty = false self._value = self.effect.run() // 执行 getter,此时会收集依赖 } return self._value } set value(newValue) { this._setter(newValue) } }懒执行与缓存机制:
内部创建了一个
ReactiveEffect,但不立即执行(effect 构造时lazy选项阻止自动运行)。依赖变更时,scheduler 仅将
_dirty置为true,并触发自身triggerRefValue,通知依赖该 computed 的 effect 重新执行。真正求值发生在
get value()中,且只有_dirty为true时才重新执行 getter,否则直接返回_value。当没有任何地方读取 computed 时,即使依赖变化也不会发生计算,这就是懒执行。
6.3 computed 与 effect 的区别
| 对比维度 | computed | effect |
|---|---|---|
| 执行时机 | 懒执行:只有被读取时才计算 | 立即执行一次,之后依赖变化时自动重新执行 |
| 返回值 | 返回一个 ref 对象,通过.value访问 | 返回一个 runner 函数,无自动返回值概念 |
| 缓存 | 依赖不变时缓存结果 | 每次重新执行,无缓存 |
| 主要用途 | 派生计算、模板中高效绑定 | 执行副作用,如 DOM 更新、watch 回调 |
| 内部实现 | 基于 effect,额外增加了脏检查与缓存 | 纯粹的依赖收集与重新执行 |
使用场景:当需要基于现有数据产生新数据并需要缓存时,用 computed;当需要在数据变化时执行某些操作(如异步请求、DOM 操作)时,用 effect(或 watch)。
七、watch 的实现原理
7.1 watch 的基本概念和使用方式
Vue 3 提供了watch和watchEffect两个 API:
watchEffect:立即执行传入的函数,并自动追踪其中的响应式依赖,依赖变化时重新执行。watch:显式指定监听一个或多个数据源,数据变化时触发回调,支持懒执行、获取旧值、深度监听等。
常用配置:
immediate: true:创建时立即执行回调。deep: true:深度监听对象内部变化。flush: 'pre' | 'post' | 'sync':控制回调的执行时机。
7.2 核心源码分析
watch 函数和watchEffect最终都调用doWatch(packages/reactivity/src/watch.ts)
javascript
function watch(source, cb, options) { return doWatch(source, cb, options) } export function watchEffect(effect, options) { return doWatch(effect, null, options) }doWatch 核心流程:
规范化监听源:
source可以是 ref、reactive 对象、getter 函数、数组等。若为 ref 或 reactive,会自动解包或深度追踪。
若为函数,视为 getter,每次运行获取当前值。
若为数组,组合多个源,回调参数为数组形式的新旧值。
创建 effect:根据配置创建
ReactiveEffect,scheduler中执行用户回调。
javascript
function doWatch(source, cb, { immediate, deep, flush } = {}) { // ... 规范化 source 为 getter let getter if (isRef(source)) { getter = () => source.value } else if (isReactive(source)) { getter = () => source deep = true // 默认对 reactive 对象开启深度监听 } else if (isFunction(source)) { getter = source } // ... // 深度监听时,在 getter 内部递归读取属性,确保触发依赖收集 if (deep) { const baseGetter = getter getter = () => traverse(baseGetter()) } let oldValue = {} const job = () => { if (cb) { const newValue = effect.run() if (deep || hasChanged(newValue, oldValue)) { cb(newValue, oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue, onCleanup) oldValue = newValue } } else { effect.run() // watchEffect } } let scheduler if (flush === 'sync') { scheduler = job } else { scheduler = () => queuePreFlushCb(job) // 默认 pre 队列,DOM 更新前执行 } const effect = new ReactiveEffect(getter, scheduler) if (cb) { if (immediate) job() else oldValue = effect.run() // 首次执行获取旧值 } else { effect.run() // watchEffect 立即执行 } return () => effect.stop() // 返回取消监听的函数 }deep 实现:traverse函数递归访问对象的所有属性,触发 getter 从而收集所有深层依赖。这样任意层级的变化都能被感知。
flush 时机处理:
pre:默认,回调在组件更新前执行(通过queuePreFlushCb加入 pre 队列)。post:回调在组件更新后执行(通过queuePostRenderEffect)。sync:同步执行,数据变化立即触发回调。
7.3 watch 与 computed 的区别
| 对比维度 | watch | computed |
|---|---|---|
| 用途 | 执行副作用(异步请求、DOM 操作) | 派生计算新值,主要用于模板绑定 |
| 执行 | 默认懒执行(immediate 可改为立即) | 懒执行,依赖不变不计算 |
| 返回值 | 返回停止监听函数 | 返回只读 ref |
| 缓存 | 无 | 有 |
| 访问新值 | 回调参数提供新旧值 | 通过.value获取当前计算结果 |
| 深度监听 | 支持 deep 递归追踪 | 依赖自动追踪,无需 deep |
| 典型场景 | “当某个数据变化时,发起网络请求” | “根据 firstName 和 lastName 计算 fullName” |
两者在响应式系统中分工明确:computed 用于纯计算,watch 用于有副作用的响应。
八、手写一个简化版的 Vue 3 响应式系统
我们将从零实现一个 mini 响应式系统,包括reactive、effect、computed、watch。
8.1 整体设计思路
核心模块:
reactive(target):返回 Proxy 代理。effect(fn):立刻执行 fn,并追踪依赖。track(target, key):在 get 中调用,收集当前 effect。trigger(target, key):在 set 中调用,运行对应的 effect。computed(getter):返回懒执行、有缓存的 ref。watch(source, cb):监听 source 变化并执行 cb。
数据结构:targetMap(WeakMap) ->depsMap(Map) ->dep(Set)。
8.2 分步实现
javascript
// mini-reactive.js let activeEffect = null const targetMap = new WeakMap() // ---- track & trigger ---- function track(target, 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) activeEffect.deps.push(dep) // 反向记录 } function trigger(target, key) { const depsMap = targetMap.get(target) if (!depsMap) return const dep = depsMap.get(key) if (dep) { // 使用新 Set 避免死循环(清理等) const effectsToRun = new Set(dep) effectsToRun.forEach(effect => { if (effect.scheduler) { effect.scheduler() } else { effect.run() } }) } } // ---- reactive ---- function reactive(target) { return new Proxy(target, { get(target, key, receiver) { const res = Reflect.get(target, key, receiver) track(target, key) if (typeof res === 'object' && res !== null) { return reactive(res) // 懒代理 } return res }, set(target, key, value, receiver) { const oldValue = target[key] const result = Reflect.set(target, key, value, receiver) if (oldValue !== value) { trigger(target, key) } return result } }) } // ---- effect ---- class ReactiveEffect { constructor(fn, scheduler = null) { this.fn = fn this.scheduler = scheduler this.deps = [] } run() { // 清理上次依赖,重新收集 cleanupEffect(this) activeEffect = this const result = this.fn() activeEffect = null return result } stop() { cleanupEffect(this) this.active = false } } function cleanupEffect(effect) { effect.deps.forEach(dep => dep.delete(effect)) effect.deps.length = 0 } function effect(fn, options = {}) { const _effect = new ReactiveEffect(fn, options.scheduler) _effect.run() const runner = _effect.run.bind(_effect) runner.effect = _effect return runner } // ---- ref ---- function ref(value) { return new RefImpl(value) } class RefImpl { constructor(value) { this._value = value this.dep = new Set() } get value() { if (activeEffect) { this.dep.add(activeEffect) } return this._value } set value(newVal) { if (newVal !== this._value) { this._value = newVal this.dep.forEach(effect => { if (effect.scheduler) effect.scheduler() else effect.run() }) } } } // ---- computed ---- function computed(getter) { let dirty = true let value const _effect = new ReactiveEffect(getter, () => { // scheduler:依赖变化时标记脏,不立即求值 if (!dirty) { dirty = true trigger(computedRef, 'value') } }) const computedRef = { get value() { track(computedRef, 'value') // 收集 computed 的依赖 if (dirty) { dirty = false value = _effect.run() } return value } } return computedRef } // ---- watch ---- function watch(source, cb, options = {}) { let getter if (typeof source === 'function') { getter = source } else if (source instanceof RefImpl) { getter = () => source.value } else { // 简化:假设为 reactive 对象,深度监听 getter = () => traverse(source) } let oldValue const job = () => { const newValue = effect.run() cb(newValue, oldValue) oldValue = newValue } const effect = new ReactiveEffect(getter, job) if (options.immediate) { job() } else { oldValue = effect.run() } } // 深度遍历依赖 function traverse(value, seen = new Set()) { if (typeof value !== 'object' || value === null || seen.has(value)) return value seen.add(value) for (const key in value) { traverse(value[key], seen) } return value }8.3 测试与验证
javascript
// 测试 reactive 和 effect const state = reactive({ count: 0, nested: { a: 1 } }) let dummy effect(() => { dummy = state.count * 2 }) console.log(dummy) // 0 state.count = 5 console.log(dummy) // 10 // 测试 computed const countRef = ref(2) const double = computed(() => countRef.value * 2) console.log(double.value) // 4 countRef.value = 3 console.log(double.value) // 6(懒计算) // 测试 watch let oldVal, newVal watch(() => state.count, (n, o) => { newVal = n; oldVal = o }) state.count = 8 console.log(oldVal, newVal) // 5 8与官方实现的差异:
未处理数组 length 与迭代器相关依赖、Map/Set 的拦截。
没有异步更新队列(同步执行),官方使用微任务批处理。
未实现 effect 调度优先级、
stop时的完全清理。缺少边界情况处理(如代理只读对象、避免死循环等)。
完整可运行代码及在线示例请查看:CodeSandBox 链接(读者可自行创建运行环境测试)。
九、总结与思考
Vue 3 响应式系统基于 Proxy 重新设计,核心思想仍然是依赖收集与触发更新,但实现上更加精简、高效。通过WeakMap -> Map -> Set三层结构管理依赖,通过懒代理提升初始化性能,通过effect统一管理副作用,通过computed和watch提供更上层的响应式工具。
本文核心回顾:
Proxy 对比 Object.defineProperty 的四大优势。
reactive / ref 的创建与依赖收集流程。
track / trigger 的源码实现与数据结构。
computed 的懒执行、缓存与 effect 的关系。
watch 的多源监听、深度追踪与回调调度。
优势与不足:
优势:支持更多数据结构、性能更佳、代码更简洁、无 set/set/delete 等补丁 API。
不足:不支持 IE,依赖 Proxy 导致 Polyfill 困难;深层嵌套大对象频繁操作时仍有优化空间。
展望:未来响应式系统可能会引入更细粒度的调度、与编译时优化(如 Vue Vapor Mode)深度结合,甚至利用 TC39 新特性(如 Signals 提案)进一步统一状态管理。
本文通过对 Vue 3 响应式源码的逐层剖析,从设计思想到核心实现,再到手写 mini 系统,力求帮助读者建立起完整的知识图谱。欢迎在评论区分享你对响应式系统的理解或疑问,我们一起探讨!
