Vue3 的响应式系统源码级 Map/Set 的惰性代理与迭代器方法拦截原理
字数 724 2025-11-28 01:08:43
Vue3 的响应式系统源码级 Map/Set 的惰性代理与迭代器方法拦截原理
一、知识描述
Vue3 的响应式系统需要对 ES6 的 Map 和 Set 集合类型进行特殊处理。由于 Map/Set 的使用特性(频繁的增删改查和迭代操作),直接代理会带来性能问题。Vue3 采用惰性代理策略,只有在访问时才对嵌套对象进行响应式转换,并通过拦截迭代器方法实现完整的响应式支持。
二、核心问题分析
- 直接代理的问题:如果对 Map/Set 的所有嵌套值立即进行深度响应式转换,会造成不必要的性能开销
- 迭代器方法的响应式:Map/Set 的 keys()、values()、entries() 等方法返回迭代器,需要特殊处理才能保证响应式
- size 属性的追踪:size 属性需要在使用时动态追踪依赖
三、惰性代理实现原理
步骤1:响应式入口判断
function reactive(target) {
// 如果是只读对象,直接返回
if (isReadonly(target)) return target
// 创建响应式代理
return createReactiveObject(
target,
mutableHandlers,
mutableCollectionHandlers, // 对 Map/Set 使用特殊处理器
reactiveMap
)
}
步骤2:集合类型特殊处理
function createReactiveObject(target, baseHandlers, collectionHandlers, proxyMap) {
// 判断目标类型
if (target instanceof Map || target instanceof Set ||
target instanceof WeakMap || target instanceof WeakSet) {
// 对集合类型使用特殊的处理器
return new Proxy(target, collectionHandlers)
}
// 普通对象使用基础处理器
return new Proxy(target, baseHandlers)
}
步骤3:惰性代理的关键 - get 拦截
const mutableCollectionHandlers = {
get(target, key, receiver) {
// 拦截内置方法
if (key === 'size') {
// 对 size 属性进行依赖收集
track(target, 'size', ITERATE_KEY)
return Reflect.get(target, 'size', target)
}
// 拦截迭代器方法
if (key === 'entries' || key === 'keys' || key === 'values') {
return function() {
// 返回包装后的迭代器,确保响应式
return target[key].call(target)
}
}
// 对 get、has 等方法进行拦截
const targetIsMap = isMap(target)
switch (key) {
case 'get':
return function(key) {
// 获取原始值,如果是对象则进行惰性响应式转换
const raw = toRaw(key)
const result = target.get(raw)
// 只有当值被使用时才进行响应式转换
return isObject(result) ? reactive(result) : result
}
case 'has':
return function(key) {
const raw = toRaw(key)
return target.has(raw)
}
case 'add':
return function(key) {
// 添加操作触发更新
trigger(target, 'add', key)
return target.add.call(target, key)
}
}
return Reflect.get(target, key, receiver)
}
}
四、迭代器方法拦截原理
步骤1:迭代器包装
// 创建响应式迭代器
function createIterableMethod(method) {
return function(...args) {
const target = toRaw(this)
// 获取原始迭代器
const innerIterator = target[method](...args)
// 对迭代器进行包装,确保迭代过程中的响应式
return {
next() {
const { value, done } = innerIterator.next()
return {
// 对迭代值进行响应式包装
value: value ? [reactive(value[0]), reactive(value[1])] : value,
done
}
},
[Symbol.iterator]() {
return this
}
}
}
}
步骤2:迭代器依赖收集
// 在迭代器方法调用时进行依赖收集
function createForEach() {
return function(callback, thisArg) {
const target = toRaw(this)
// 遍历时收集依赖
track(target, 'iterate', ITERATE_KEY)
// 包装回调函数,确保参数是响应式的
const wrappedCallback = (value, key) => {
callback(reactive(value), reactive(key), this)
}
return target.forEach.call(target, wrappedCallback, thisArg)
}
}
五、size 属性的特殊处理
步骤1:动态追踪机制
const collectionHandlers = {
get(target, key, receiver) {
if (key === 'size') {
// 每次访问 size 时都重新收集依赖
track(target, 'size', ITERATE_KEY)
// 返回实际的 size 值
return Reflect.get(target, 'size', target)
}
// 其他方法处理...
}
}
步骤2:size 变化的触发更新
// 在 add、delete、clear 等方法中触发 size 相关的更新
function createAddMethod() {
return function(value) {
value = toRaw(value)
const hadKey = this.has(value)
// 调用原始方法
const result = target.add.call(this, value)
if (!hadKey) {
// 只有真正添加了新元素才触发更新
trigger(this, 'add', value)
trigger(this, 'size', ITERATE_KEY) // 特别触发 size 更新
}
return result
}
}
六、性能优化策略
步骤1:避免不必要的响应式转换
function get(target, key) {
const result = target.get(key)
// 只有对象类型才需要响应式转换,基本类型直接返回
return isObject(result) ? reactive(result) : result
}
步骤2:缓存机制
// 使用 WeakMap 缓存已转换的响应式对象
const reactiveCache = new WeakMap()
function getReactive(value) {
if (!isObject(value)) return value
// 查找缓存
let observed = reactiveCache.get(value)
if (!observed) {
// 惰性创建响应式对象
observed = reactive(value)
reactiveCache.set(value, observed)
}
return observed
}
七、总结
Vue3 对 Map/Set 的响应式处理采用了精细化的惰性代理策略:
- 惰性转换:只在访问时对嵌套对象进行响应式转换
- 迭代器拦截:通过包装迭代器方法确保遍历操作的响应式
- size 动态追踪:size 属性在使用时动态收集依赖
- 性能优化:避免不必要的转换操作,使用缓存提高性能
这种设计既保证了响应式的完整性,又最大程度减少了性能开销,体现了 Vue3 响应式系统在性能与功能之间的精细平衡。