发布于 2026-01-10, 更新于 2026-01-11
Vue 3 的响应式系统基于 ES6 Proxy,通过拦截对象的访问和修改,实现依赖收集与更新机制,其核心源码位于 packages/reactivity 目录。
学习响应式原理、阅读源码,可以使得在用响应式数据时心中有数,也可在 Vue 版本更新里识别版本差异的影响。
Proxy 用于创建一个代理对象,可以拦截并重新定义对目标对象的基本操作。传参:
handler 用于拦截对象的基本操作:
get 拦截器 - 拦截属性读取:
const obj = { name: 'Vue', version: 3.5 }
const proxy = new Proxy(obj, {
get(target, key, receiver) {
console.log(`访问属性: ${key}`)
return Reflect.get(target, key, receiver)
}
})
console.log(proxy.name) // 输出: 访问属性: name \n Vue
console.log(proxy.version) // 输出: 访问属性: version \n 3.5
set 拦截器 - 拦截属性设置:
const obj = { count: 0 }
const proxy = new Proxy(obj, {
set(target, key, value, receiver) {
console.log(`设置属性: ${key} = ${value}`)
return Reflect.set(target, key, value, receiver)
}
})
proxy.count = 5 // 输出: 设置属性: count = 5
proxy.name = 'Vue' // 输出: 设置属性: name = Vue
对比 Vue 2 使用的 defineProperty ,Proxy 有许多好处,如整个对象维度监听而不是具体属性,新属性不需要 Vue.set 这样的语法;可以监听“新增 / 删除属性”;可直接拦截数组索引、length、支持 Map / Set 等。
Reflect 是一个内置对象,提供拦截 JavaScript 操作的方法。这些方法与 proxy handler 的方法相同。
get 方法,等同于 obj.name,但更安全:
// Reflect.get(target, propertyKey[, receiver])
const obj = { name: 'Vue', version: 3.5 }
console.log(Reflect.get(obj, 'name')) // 输出: Vue
console.log(Reflect.get(obj, 'version')) // 输出: 3.5
set 方法:
// Reflect.set(target, propertyKey, value[, receiver])
const obj = { count: 0 }
Reflect.set(obj, 'count', 5)
console.log(obj.count) // 输出: 5
Reflect.set(obj, 'name', 'Vue')
console.log(obj.name) // 输出: Vue
receiver 参数用于保证正确上下文:
Vue 源码中有这一句:isRef(target) ? target : receiver ,当被访问的对象本身是 ref 时,不使用 proxy 作为 receiver,而是使用 ref 自身,避免 this 绑定到代理对象从而破坏 ref 的语义,确保 ref 在方法调用中的行为保持一致。
WeakMap 是一种键值对的集合,优势 :
在 Vue 3 中,WeakMap 被广泛用于存储各种映射关系。
reactive 方法和 ref 方法都可以创建响应式对象,对于非简单类型数据, ref 内部也是调用了 reactive 来创建。
这里实现一版最基础的 reactive 方法:
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
// 依赖收集
track(target, key)
return Reflect.get(target, key, receiver)
},
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
}
})
}
后续实现依赖收集和触发更新函数,先打印:
function track(target, key) {
console.log(`收集依赖: ${target.constructor.name}[${key}]`)
}
function trigger(target, key) {
console.log(`触发更新: ${target.constructor.name}[${key}]`)
}
使用示例:
const obj = reactive({ count: 0 })
console.log(obj.count) // 收集依赖: Object[count]
obj.count = 5 // 触发更新: Object[count]
在 Vue 源码里,track 方法和 trigger 方法做在了 dep.ts 文件中,用于记录和执行 effect 副作用方法。
依赖收集,需要记录当前活动的副作用函数(effect)与被访问的响应式数据之间的依赖关系。
targetMap 的结构如下:
targetMap = {
[原始对象1]: {
'属性名1': Set { effect1, effect2, ... },
'属性名2': Set { effect3, effect4, ... },
},
[原始对象2]: {
'属性名1': Set { effect5, ... },
}
}
如上文所述, track 在 proxy 对象的 get 中触发。模拟一个简化的 track 依赖收集:
const targetMap = new WeakMap()
function track(target, key) {
// 如果当前没有活动的 effect,不进行依赖收集
if (!activeEffect) return
// 获取 target 对应的依赖映射
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
// 获取 key 对应的 effect 集合
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
// 将当前 effect 添加到依赖集合中
dep.add(activeEffect)
}
trigger 触发更新,当 proxy 的 set 调用,当响应式数据发生变化时,通知所有依赖该数据的副作用函数重新执行。
function trigger(target, key) {
// 从 targetMap 中获取依赖映射
const depsMap = targetMap.get(target)
if (!depsMap) {
return
}
// 获取 key 对应的 effect 集合
const effects = depsMap.get(key)
if (effects) {
// 执行所有依赖当前 key 的 effect
effects.forEach(effect => {
effect() // 触发更新,重新执行 effect
})
}
}
模拟 effect 副作用函数:
let activeEffect = null
function effect(fn) {
const effectFn = () => {
activeEffect = effectFn // 设置当前活动的 effect
fn() // 执行原函数,在执行过程中会触发 track
activeEffect = null // 重置
}
effectFn() // 立即执行一次
}
使用 effect 函数:
const obj = obj({ a: 1, b: 2 })
let sum
effect(() => {
sum = obj.a + obj.b
})
let product
effect(() => {
product = obj.a * obj.b
})
响应式更新使用效果:
console.log(sum, product) // 3 2
reactiveObj.a = 5
console.log(sum, product) // 7 10
reactiveObj.b = 10
console.log(sum, product) // 15 50
ref 实现位于 ref.ts 文件,实际创建 ref 时会创建一个 RefImpl 类对象:
_value 属性判断如果不是简单类型,会创建为 reactive ,普通值直接记录实现一个 ref 方法,暂不考虑复杂类型,且为了简化,利用当前对象和value属性调用前面的 track 和 trigger 方:
function ref(raw) {
let _value = raw
const res = {
get value() {
track(res, 'value')
return _value
},
set value(x) {
if (_value !== x) {
_value = x
trigger(res, 'value')
}
}
}
return res
}
将前面 demo 的 b 属性改为通过 ref 创建:
const reactiveObj = reactive({ a: 1 })
const b = ref(2)
let sum
effect(() => {
sum = reactiveObj.a + b.value
})
let product
effect(() => {
product = reactiveObj.a * b.value
})
console.log(sum, product) // 3 2
reactiveObj.a = 5
console.log(sum, product) // 7 10
b.value = 10
console.log(sum, product) // 15 50
computed 实现位于 computed.ts 文件,实际创建 ComputedRefImpl 对象:
_value 和 dep 属性,跟 ref 类似,也需要进行依赖收集模拟一个简单的实现,只考虑 getter:
function computed(getter) {
let _value
const res = {
get value() {
track(res, 'value')
return _value
}
}
effect(() => {
const v = getter()
if (v !== _value) {
_value = v
trigger(res, 'value')
}
})
return res
}
使用为:
const reactiveObj = reactive({ a: 1 })
const b = ref(2)
let sum = computed(() => reactiveObj.a + b.value)
let product = computed(() => reactiveObj.a * b.value)
console.log(sum.value, product.value) // 3 2
reactiveObj.a = 5
console.log(sum.value, product.value) // 7 10
b.value = 10
console.log(sum.value, product.value) // 15 50
这个 demo 实现了 Vue 响应式的观察者/发布订阅的最小模型,被观察者是数据的“属性级别状态”,观察者是 effect 副作用方法。观察关系在 effect 执行期间,通过读取数据自动建立(如 Proxy 对象 get 方法、ref 返回对象中的 get value 方法),当状态变化时,被观察者通知所有观察者重新执行,从而完成响应式更新。
在实际的 Vue 源码中,还需要处理各种复杂类型;需要处理 effect 嵌套场景,非单一 activeEffect;依赖需要进行清理;computed 需要懒执行;effect需调度执行等等。可参照 Vue 源码进行阅读,从而加深对 Vue 响应式实现的理解。
以上所有实现,整体代码如下:
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
// 依赖收集
track(target, key)
return Reflect.get(target, key, receiver)
},
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
}
})
}
// 模拟依赖收集和触发函数
const targetMap = new WeakMap()
let activeEffect = null
function track(target, key) {
// 如果当前没有活动的 effect,不进行依赖收集
if (!activeEffect) return
// 获取 target 对应的依赖映射
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
// 获取 key 对应的 effect 集合
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
// 将当前 effect 添加到依赖集合中
dep.add(activeEffect)
}
function trigger(target, key) {
// 从 targetMap 中获取依赖映射
const depsMap = targetMap.get(target)
if (!depsMap) {
return
}
// 获取 key 对应的 effect 集合
const effects = depsMap.get(key)
if (effects) {
// 执行所有依赖当前 key 的 effect
effects.forEach(effect => {
effect() // 触发更新,重新执行 effect
})
}
}
function effect(fn) {
const effectFn = () => {
activeEffect = effectFn // 设置当前活动的 effect
fn() // 执行原函数,在执行过程中会触发 track
activeEffect = null // 重置
}
effectFn() // 立即执行一次
}
function ref(raw) {
let _value = raw
const res = {
get value() {
track(res, 'value')
return _value
},
set value(x) {
if (_value !== x) {
_value = x
trigger(res, 'value')
}
}
}
return res
}
function computed(getter) {
let _value
const res = {
get value() {
track(res, 'value')
return _value
}
}
effect(() => {
const v = getter()
if (v !== _value) {
_value = v
trigger(res, 'value')
}
})
return res
}
const reactiveObj = reactive({ a: 1 })
const b = ref(2)
let sum = computed(() => reactiveObj.a + b.value)
let product = computed(() => reactiveObj.a * b.value)
let view = computed(() => `sum: ${sum.value}, product: ${product.value}`)
console.log(view.value) // sum: 3, product: 2
reactiveObj.a = 5
console.log(view.value) // sum: 7, product: 10
b.value = 10
console.log(view.value) // sum: 15, product: 50