Vue3 的响应式系统是框架的核心,相比 Vue2 发生了根本性的变化。本文将从源码层面深入解析 Vue3 响应式系统的实现原理,包括 Proxy 的使用、依赖收集机制、副作用函数调度等核心概念。
从 Object.defineProperty 到 Proxy
Vue2 使用 Object.defineProperty 实现响应式,但存在一些限制:
// Vue2 的限制
const vm = new Vue({
data: {
obj: { a: 1 }
}
})
// ❌ 无法检测对象属性的添加
vm.obj.b = 2 // 无响应
// ❌ 无法检测数组索引和长度的变化
vm.arr[0] = 'new' // 无响应
vm.arr.length = 0 // 无响应
Vue3 使用 Proxy 彻底解决了这些问题:
// Proxy 基本用法
const target = { a: 1 }
const proxy = new Proxy(target, {
get(target, key, receiver) {
console.log(`get ${key}`)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
console.log(`set ${key} = ${value}`)
return Reflect.set(target, key, value, receiver)
}
})
proxy.a // get a
proxy.a = 2 // set a = 2
"Proxy 的核心优势是可以拦截对象的任何操作,包括属性访问、赋值、删除、枚举等。这让 Vue3 能够实现真正完整的响应式系统。"
reactive 实现原理
让我们手写一个简化版的 reactive:
let activeEffect = null
const targetMap = new WeakMap()
// 依赖收集
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)
}
// 触发更新
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => effect())
}
}
// 创建响应式对象
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
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) {
trigger(target, key)
}
return result
}
})
}
依赖收集的数据结构
// WeakMap// WeakMap<Targetlt;Target, Map<Key, Set<Effect>>>
//
// 示例:
// targetMap = {
// obj1: Map {
// 'name': Set [effect1, effect2],
// 'age': Set [effect1, effect3]
// },
// obj2: Map {
// 'count': Set [effect2]
// }
// }
ref 实现原理
ref 用于包装基本类型值,使其成为响应式:
class RefImpl {
constructor(value) {
this._value = toReactive(value)
this.__v_isRef = true
this.dep = new Set()
}
get value() {
// 收集依赖
if (activeEffect) {
this.dep.add(activeEffect)
}
return this._value
}
set value(newValue) {
if (this._value !== newValue) {
this._value = toReactive(newValue)
// 触发更新
this.dep.forEach(effect => effect())
}
}
}
function toReactive(value) {
if (typeof value === 'object' && value !== null) {
return reactive(value)
}
return value
}
function ref(value) {
return new RefImpl(value)
}
effect 副作用函数
effect 是响应式系统的核心,用于包装副作用函数:
function effect(fn, options = {}) {
const effectFn = () => {
try {
activeEffect = effectFn
// 执行函数,触发 getter,收集依赖
return fn()
} finally {
activeEffect = null
}
}
effectFn.options = options
effectFn.deps = [] // 存储依赖关系
// 立即执行
if (!options.lazy) {
effectFn()
}
return effectFn
}
使用示例
const state = reactive({ count: 0 })
effect(() => {
console.log(`count is: ${state.count}`)
})
state.count++ // 输出: count is: 1
依赖收集与触发详解
完整的 track 实现
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()))
}
// 双向绑定:effect 记录依赖的 key
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
}
完整的 trigger 实现
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (!dep) return
// 复制一份避免在遍历时修改
const effects = [...dep]
effects.forEach(effect => {
// 避免无限循环
if (effect !== activeEffect) {
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
})
}
computed 计算属性
computed 本质上是一个特殊的 effect,具有懒执行和缓存特性:
class ComputedRefImpl {
constructor(getter) {
this._value = undefined
this._dirty = true // 是否需要重新计算
this._getter = getter
this.dep = new Set()
// 创建 effect,但懒执行
this.effect = effect(getter, {
lazy: true,
scheduler: () => {
if (!this._dirty) {
this._dirty = true
// 计算属性变化,触发依赖更新
trigger(this, 'value')
}
}
})
}
get value() {
// 收集依赖
if (activeEffect) {
this.dep.add(activeEffect)
}
// 懒执行 + 缓存
if (this._dirty) {
this._value = this.effect()
this._dirty = false
}
return this._value
}
}
function computed(getter) {
return new ComputedRefImpl(getter)
}
使用示例
const state = reactive({ count: 0 })
const doubled = computed(() => state.count * 2)
console.log(doubled.value) // 0
state.count++ // 不会立即计算
console.log(doubled.value) // 2 (访问时才计算)
console.log(doubled.value) // 2 (使用缓存)
watch 侦听器原理
watch 是基于 effect 实现的:
// 简化版 watch 实现
function watch(source, cb, options = {}) {
let getter
let oldValue
// 确定 getter
if (typeof source === 'function') {
getter = source
} else {
getter = () => traverse(source)
}
// 创建 effect
const effectFn = effect(() => getter(), {
lazy: true,
scheduler: () => {
// 在调度器中执行回调
const newValue = effectFn()
if (newValue !== oldValue) {
cb(newValue, oldValue)
oldValue = newValue
}
}
})
// 首次执行
if (options.immediate) {
oldValue = effectFn()
cb(oldValue, undefined)
} else {
oldValue = effectFn()
}
}
// 遍历对象,触发所有属性的 getter
function traverse(value) {
if (typeof value !== 'object' || value === null) {
return value
}
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
traverse(value[i])
}
} else {
for (const key in value) {
traverse(value[key])
}
}
return value
}
调度器与批处理
Vue3 使用调度器实现批处理,避免多次重复渲染:
// 调度队列
let queue = []
let isFlushing = false
function queueJob(job) {
if (!queue.includes(job)) {
queue.push(job)
}
if (!isFlushing) {
isFlushing = true
Promise.resolve().then(flushJobs)
}
}
function flushJobs() {
isFlushing = false
// 排序:确保父组件先于子组件更新
queue.sort((a, b) => a.id - b.id)
try {
for (let i = 0; i < queue.length; i++) {
queue[i]()
}
} finally {
queue.length = 0
}
}
// 在 effect 中使用调度器
effect(() => {
console.log(state.count)
}, {
scheduler: (effect) => {
queueJob(effect)
}
})
批处理示例
const state = reactive({ count: 0 })
effect(() => {
console.log(`count: ${state.count}`)
}, {
scheduler: (fn) => queueJob(fn)
})
// 多次更新只会触发一次
state.count++
state.count++
state.count++
// 输出: count: 3
总结
Vue3 的响应式系统是一个精妙的设计,核心思想是通过 Proxy 拦截对象操作,结合 WeakMap、Map、Set 数据结构实现依赖收集和更新触发。
核心概念
- Proxy:拦截对象操作,实现响应式基础
- effect:包装副作用函数,实现依赖收集
- track/trigger:依赖收集和更新触发机制
- WeakMap:存储对象与依赖的关系,支持垃圾回收
- 调度器:批处理更新,优化性能
与 Vue2 的对比
特性
Vue2
Vue3
实现方式
Object.defineProperty
Proxy
新增属性
需要 Vue.set
自动响应
数组变化
需要变异方法
全部响应
性能
初始化慢
初始化快
内存占用
较高
较低(WeakMap)