Pixiv - KiraraShss
深入 Vue3 响应式原理:从 Proxy 到依赖收集的完整实现
1380 字
7 分钟
深入 Vue3 响应式原理:从 Proxy 到依赖收集的完整实现
深入 Vue3 响应式原理:从 Proxy 到依赖收集的完整实现
Vue3 的响应式系统是整个框架的核心,相比 Vue2 的 Object.defineProperty,Vue3 使用 Proxy 实现了更强大、更完整的响应式能力。本文将从零手写一个简化版的 Vue3 响应式系统,深入理解其底层原理。
一、为什么要用 Proxy 替代 defineProperty?
Vue2 的 Object.defineProperty 存在几个致命缺陷:
// Vue2 无法检测到以下变化const obj = { a: 1 }obj.b = 2 // ❌ 新增属性无法响应delete obj.a // ❌ 删除属性无法响应arr[0] = 'new' // ❌ 通过索引修改数组无法响应arr.length = 0 // ❌ 修改 length 无法响应而 Proxy 可以拦截对象的几乎所有操作:
const handler = { get(target, key, receiver) { /* 读取拦截 */ }, set(target, key, value, receiver) { /* 设置拦截 */ }, deleteProperty(target, key) { /* 删除拦截 */ }, has(target, key) { /* in 操作符拦截 */ }, ownKeys(target) { /* for...in 拦截 */ }}二、核心实现:reactive 和 effect
2.1 全局依赖收集容器
// 当前正在执行的 effectlet activeEffect = null// effect 栈,处理嵌套 effectconst effectStack = []// 存储依赖关系:target -> key -> effectsconst targetMap = new WeakMap()
function track(target, key) { if (!activeEffect) return
let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) }
let deps = depsMap.get(key) if (!deps) { depsMap.set(key, (deps = new Set())) }
deps.add(activeEffect) // 反向收集,用于 cleanup activeEffect.deps.push(deps)}
function trigger(target, key) { const depsMap = targetMap.get(target) if (!depsMap) return
const effects = depsMap.get(key) if (!effects) return
// 创建新 Set 避免无限循环 const effectsToRun = new Set() effects.forEach(effect => { // 避免自身触发自身 if (effect !== activeEffect) { effectsToRun.add(effect) } })
effectsToRun.forEach(effect => { // 如果有调度器,使用调度器执行 if (effect.options.scheduler) { effect.options.scheduler(effect) } else { effect() } })}2.2 reactive 实现
const reactiveMap = new WeakMap()
function reactive(target) { // 避免重复代理 if (reactiveMap.has(target)) { return reactiveMap.get(target) }
const proxy = new Proxy(target, { get(target, key, receiver) { // 标记已被代理 if (key === '__v_isReactive') return true
const result = Reflect.get(target, key, receiver)
// 收集依赖 track(target, key)
// 深层代理:惰性转换 if (typeof result === 'object' && result !== null) { return reactive(result) }
return result },
set(target, key, value, receiver) { const oldValue = target[key] const result = Reflect.set(target, key, value, receiver)
// 值变化时才触发 if (oldValue !== value && (oldValue === oldValue || value === value)) { trigger(target, key) }
return result },
deleteProperty(target, key) { const hadKey = Object.prototype.hasOwnProperty.call(target, key) const result = Reflect.deleteProperty(target, key)
if (hadKey && result) { trigger(target, key) }
return result } })
reactiveMap.set(target, proxy) return proxy}2.3 effect 实现
function effect(fn, options = {}) { const effectFn = () => { // 清除旧依赖,避免分支切换导致的遗留依赖 cleanup(effectFn)
activeEffect = effectFn effectStack.push(effectFn)
const result = fn()
effectStack.pop() activeEffect = effectStack[effectStack.length - 1]
return result }
effectFn.deps = [] effectFn.options = options
if (!options.lazy) { effectFn() }
return effectFn}
function cleanup(effectFn) { for (const deps of effectFn.deps) { deps.delete(effectFn) } effectFn.deps.length = 0}三、进阶:computed 的实现
computed 本质上是一个带有缓存和惰性求值的 effect:
function computed(getter) { let value let dirty = true // 脏标记
const effectFn = effect(getter, { lazy: true, scheduler() { if (!dirty) { dirty = true // 手动触发依赖更新 trigger(obj, 'value') } } })
const obj = { get value() { if (dirty) { value = effectFn() dirty = false } // 收集外层依赖 track(obj, 'value') return value } }
return obj}使用示例
const state = reactive({ price: 100, quantity: 3 })
const total = computed(() => { console.log('computed 执行') return state.price * state.quantity})
// 第一次访问,执行 getterconsole.log(total.value) // computed 执行 → 300
// 第二次访问,命中缓存console.log(total.value) // 300(不打印 "computed 执行")
// 修改依赖,标记为 dirtystate.price = 200
// 再次访问,重新计算console.log(total.value) // computed 执行 → 600四、watch 的实现
watch 基于 effect + scheduler 实现:
function watch(source, callback, options = {}) { let getter
if (typeof source === 'function') { getter = source } else { // 递归读取所有属性,触发 track getter = () => traverse(source) }
let oldValue, newValue let cleanup
function onInvalidate(fn) { cleanup = fn }
const job = () => { newValue = effectFn() // 过期回调,用于竞态处理 if (cleanup) cleanup() callback(newValue, oldValue, onInvalidate) oldValue = newValue }
const effectFn = effect(getter, { lazy: true, scheduler: () => { // flush: 'post' 时放入微任务队列 if (options.flush === 'post') { Promise.resolve().then(job) } else { job() } } })
if (options.immediate) { job() } else { oldValue = effectFn() }}
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}竞态问题处理
watch( () => state.id, async (newId, oldId, onInvalidate) => { let expired = false onInvalidate(() => { expired = true })
const data = await fetchData(newId)
// 如果已过期(新请求已发出),忽略结果 if (!expired) { state.data = data } })五、Vue3 响应式的性能优化
5.1 惰性代理
Vue3 不会一次性将所有嵌套对象转为响应式,而是在访问时才代理:
// get 中的惰性转换if (typeof result === 'object' && result !== null) { return reactive(result) // 访问时才代理}5.2 位运算标记
Vue3 源码中大量使用位运算来标记组件状态:
const enum ShapeFlags { ELEMENT = 1, // 0001 STATEFUL_COMPONENT = 1 << 2, // 0100 TEXT_CHILDREN = 1 << 3, // 1000 ARRAY_CHILDREN = 1 << 4, // 10000}
// 判断类型:位与if (vnode.shapeFlag & ShapeFlags.ELEMENT) { /* ... */ }
// 组合类型:位或vnode.shapeFlag = ShapeFlags.ELEMENT | ShapeFlags.ARRAY_CHILDREN5.3 调度器批量更新
const queue = new Set()let isFlushing = false
function queueJob(job) { queue.add(job) if (!isFlushing) { isFlushing = true Promise.resolve().then(() => { queue.forEach(job => job()) queue.clear() isFlushing = false }) }}总结
Vue3 响应式系统的核心设计思想:
| 特性 | 实现方式 |
|---|---|
| 数据拦截 | Proxy + Reflect |
| 依赖收集 | WeakMap → Map → Set |
| 副作用管理 | effect + effectStack |
| 缓存计算 | computed (lazy + dirty) |
| 异步调度 | scheduler + 微任务队列 |
| 竞态处理 | onInvalidate 回调 |
理解这些底层原理,不仅能帮你更好地使用 Vue3,也能在遇到响应式相关 Bug 时快速定位问题。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
深入 Vue3 响应式原理:从 Proxy 到依赖收集的完整实现
https://boke.hackerdream.xyz/posts/vue3-reactivity-deep-dive/ 相关文章 智能推荐
1
JS Proxy 核心原理、实战痛点与常用解决方案
JavaScript 进阶 2026-04-28
2
Vue 3.5 深度解析:Alien Signals 响应式重构与五大新特性全面拆解
Vue 深入 2026-04-06
3
Vue3 编译器优化:静态提升、补丁标记与 Block Tree 的实现原理
Vue 深入 2026-03-04
4
Vue3 依赖注入深入:provide/inject 的架构设计与最佳实践
Vue 深入 2026-01-21
5
Vue3 自定义渲染器:从零实现一个 Canvas 渲染器
Vue 深入 2026-03-07
随机文章 随机推荐