Vue3 的响应式系统源码级 Map/Set 的响应式处理与 size 属性追踪原理
字数 1393 2025-11-24 00:29:29
Vue3 的响应式系统源码级 Map/Set 的响应式处理与 size 属性追踪原理
知识点描述
Map 和 Set 是 JavaScript 中常用的数据结构,Vue3 的响应式系统需确保对它们的操作(如添加、删除、修改)能触发依赖更新。由于 Map/Set 的操作方式(如 set()、add()、delete())与普通对象不同,需特殊处理。核心难点在于:
- 如何拦截 Map/Set 的方法调用以实现依赖收集和触发更新。
- 如何正确追踪
size属性的依赖(因size是访问器属性,其变化与数据操作关联)。
下面逐步解析其实现机制。
1. 响应式 Map/Set 的创建
当对原始 Map/Set 执行 reactive() 时,Vue3 会检测数据类型,若为 Map/Set 则进入特殊处理逻辑:
function reactive(target) {
if (target instanceof Map || target instanceof Set) {
return createReactiveMapOrSet(target);
}
// 普通对象的处理...
}
在 createReactiveMapOrSet 中,Vue3 会创建一个代理对象,并重写关键方法(如 get、set、add、delete 等),确保操作可被拦截。
2. 关键方法的拦截与依赖收集
Vue3 通过重写 Map/Set 的方法实现拦截。以 Map 的 set 方法为例:
- 拦截逻辑:
代理对象的set方法被调用时,先获取原始值(通过toRaw避免代理嵌套),执行原始set方法,随后触发依赖更新。function set(key, value) { const rawKey = toRaw(key); // 避免 key 本身是响应式对象 const rawValue = toRaw(value); const target = toRaw(this); // 获取原始 Map const hadKey = target.has(rawKey); const oldValue = target.get(rawKey); target.set(rawKey, rawValue); // 执行原始操作 if (!hadKey) { trigger(target, 'ADD', key); // 触发 ADD 类型的更新 } else if (oldValue !== value) { trigger(target, 'SET', key); // 触发 SET 类型的更新 } } - 依赖收集:
在get拦截中,若访问的是size或迭代方法(如keys、values),会调用track收集依赖:function get(target, key) { if (key === 'size') { track(target, 'iterate'); // 对 size 的访问收集到 'iterate' 依赖 return Reflect.get(target, key); } // 其他方法处理... }
3. size 属性的依赖追踪机制
size 是 Map/Set 的访问器属性,其值随数据增减而变化。Vue3 通过以下步骤确保其响应性:
- 访问追踪:
当读取size时,触发get拦截,调用track(target, 'iterate'),将当前副作用函数(effect)关联到'iterate'依赖集合。 - 更新触发:
当执行set、add、delete等修改数据的方法时,除了触发对应键的更新,还会额外触发'iterate'依赖:
这样,当数据数量变化时,所有依赖function trigger(target, type, key) { // 触发与 key 相关的依赖 if (key !== void 0) { triggerEffects(depsMap.get(key)); } // 对 ADD/DELETE 操作,触发 iterate 依赖(因 size 变化) if (type === 'ADD' || type === 'DELETE') { triggerEffects(depsMap.get('iterate')); } }size的副作用函数会重新执行。
4. 迭代方法的响应式处理
Map/Set 的 keys()、values()、entries() 等方法返回迭代器,Vue3 通过包装迭代器确保其响应性:
- 迭代器拦截:
调用这些方法时,返回一个自定义迭代器。在迭代过程中,每次调用next()时都会追踪当前键值,确保依赖收集。 - 清理机制:
当执行delete等操作时,迭代器关联的依赖会被清理,避免无效更新。
总结
Vue3 通过代理 Map/Set 的方法调用,结合 track 和 trigger 机制,实现了对数据操作和 size 属性的精准依赖追踪。核心在于:
- 拦截方法调用,区分操作类型(ADD/SET/DELETE)。
- 对
size和迭代方法收集到'iterate'依赖集合。 - 数据变化时同时触发键相关依赖和
'iterate'依赖。
这种设计确保了 Map/Set 在响应式系统中的行为与普通对象一致,同时兼顾了性能与准确性。