Vue3 的响应式系统源码级 Map/Set 的响应式处理与 size 属性追踪原理
字数 695 2025-11-23 22:37:05
Vue3 的响应式系统源码级 Map/Set 的响应式处理与 size 属性追踪原理
知识点描述
Map 和 Set 是 JavaScript 中常用的数据结构,Vue3 的响应式系统需要特殊处理这两种集合类型。本知识点将深入分析 Vue3 如何实现对 Map 和 Set 的响应式代理,特别是 size 属性的依赖收集和更新触发机制。
解题过程
1. 基础代理创建
// 创建响应式 Map 的基本结构
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
// 对特殊属性和方法进行拦截
if (key === 'size') {
// 特殊处理 size 属性
track(target, 'size')
return Reflect.get(target, 'size', target)
}
// 拦截操作方法
const method = Reflect.get(target, key, receiver)
if (typeof method === 'function') {
return function(...args) {
// 在执行方法前后进行依赖追踪和触发更新
const result = method.apply(target, args)
trigger(target, 'size') // 方法执行后触发 size 相关更新
return result
}
}
return Reflect.get(target, key, receiver)
}
})
}
2. size 属性的特殊处理原理
- 问题分析:Map 的 size 属性是一个访问器属性,每次访问都会返回当前集合的大小
- 依赖收集:当在 effect 中访问
map.size时,需要建立 size 属性与当前 effect 的依赖关系 - 更新触发:当执行
set.add()、map.set()等改变大小的操作时,需要触发所有依赖 size 的 effect
// 详细的 size 属性处理
function createReactiveMap(map) {
return new Proxy(map, {
get(target, key, receiver) {
// 处理 size 属性
if (key === 'size') {
// 关键步骤:建立 size 属性与当前 effect 的依赖关系
track(target, ITERATE_KEY) // 使用特殊符号标记 size 依赖
return Reflect.get(target, 'size', target)
}
// 返回包装后的操作方法
return target[key].bind(target)
}
})
}
3. 操作方法的重写与优化
Vue3 不会简单包装所有方法,而是针对性地处理会改变集合大小的方法:
// 重写会改变集合大小的方法
const mutableInstrumentations = {
add(key) {
const target = this._raw // 原始对象
const hadKey = target.has(key)
// 执行原始操作
const result = target.add(key)
// 只有当键不存在时才触发更新(避免重复触发)
if (!hadKey) {
trigger(target, 'add', key)
trigger(target, 'size') // 触发 size 相关更新
}
return result
},
set(key, value) {
const target = this._raw
const hadKey = target.has(key)
const oldValue = target.get(key)
const result = target.set(key, value)
if (!hadKey) {
// 新增键值对
trigger(target, 'add', key)
trigger(target, 'size')
} else if (value !== oldValue) {
// 修改已存在的值
trigger(target, 'set', key)
}
return result
},
delete(key) {
const target = this._raw
const hadKey = target.has(key)
const result = target.delete(key)
if (hadKey) {
trigger(target, 'delete', key)
trigger(target, 'size') // 删除后大小改变,触发更新
}
return result
},
clear() {
const target = this._raw
const hadItems = target.size > 0
const result = target.clear()
if (hadItems) {
trigger(target, 'clear')
trigger(target, 'size') // 清空后大小归零
}
return result
}
}
4. 迭代方法的响应式处理
对于 keys()、values()、entries() 等迭代方法,也需要特殊处理:
// 迭代方法的响应式包装
function createIterableMethod(method) {
return function(...args) {
const target = this._raw
// 建立迭代依赖关系
track(target, ITERATE_KEY)
// 返回包装后的迭代器
const innerIterator = method.apply(target, args)
return {
next() {
const { value, done } = innerIterator.next()
return {
value: value ? reactive(value) : value,
done
}
},
[Symbol.iterator]() {
return this
}
}
}
}
5. 完整的依赖收集与触发机制
// 依赖收集系统
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)
activeEffect.deps.push(dep) // 建立双向联系用于清理
}
function trigger(target, type, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
// 收集需要触发的依赖
const effects = new Set()
// 添加特定键的依赖
if (key !== void 0) {
const dep = depsMap.get(key)
dep && dep.forEach(effect => effects.add(effect))
}
// 添加 size/迭代相关的依赖
if (type === 'add' || type === 'delete' || type === 'clear') {
const iterateDep = depsMap.get(ITERATE_KEY)
iterateDep && iterateDep.forEach(effect => effects.add(effect))
// size 属性依赖也使用 ITERATE_KEY
const sizeDep = depsMap.get('size')
sizeDep && sizeDep.forEach(effect => effects.add(effect))
}
// 触发所有收集到的依赖
effects.forEach(effect => {
if (effect.scheduler) {
effect.scheduler()
} else {
effect()
}
})
}
6. 性能优化策略
- 惰性代理:只有在访问时才创建嵌套对象的响应式代理
- 避免重复触发:在
add和set操作前检查键是否已存在 - 精确依赖:只为实际被访问的属性建立依赖关系
- 批量更新:通过调度器实现多个操作的批量更新
这种设计确保了 Map/Set 的响应式处理既准确又高效,能够精确追踪 size 属性的依赖关系,在集合大小发生变化时正确触发组件更新。