Vue3 的响应式系统源码级 Map/Set 的响应式处理与 size 属性追踪原理
字数 1393 2025-11-24 00:29:29

Vue3 的响应式系统源码级 Map/Set 的响应式处理与 size 属性追踪原理

知识点描述
Map 和 Set 是 JavaScript 中常用的数据结构,Vue3 的响应式系统需确保对它们的操作(如添加、删除、修改)能触发依赖更新。由于 Map/Set 的操作方式(如 set()add()delete())与普通对象不同,需特殊处理。核心难点在于:

  1. 如何拦截 Map/Set 的方法调用以实现依赖收集和触发更新。
  2. 如何正确追踪 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 会创建一个代理对象,并重写关键方法(如 getsetadddelete 等),确保操作可被拦截。

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 或迭代方法(如 keysvalues),会调用 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' 依赖集合。
  • 更新触发
    当执行 setadddelete 等修改数据的方法时,除了触发对应键的更新,还会额外触发 '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 的方法调用,结合 tracktrigger 机制,实现了对数据操作和 size 属性的精准依赖追踪。核心在于:

  1. 拦截方法调用,区分操作类型(ADD/SET/DELETE)。
  2. size 和迭代方法收集到 'iterate' 依赖集合。
  3. 数据变化时同时触发键相关依赖和 'iterate' 依赖。
    这种设计确保了 Map/Set 在响应式系统中的行为与普通对象一致,同时兼顾了性能与准确性。
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 则进入特殊处理逻辑: 在 createReactiveMapOrSet 中,Vue3 会创建一个代理对象,并重写关键方法(如 get 、 set 、 add 、 delete 等),确保操作可被拦截。 2. 关键方法的拦截与依赖收集 Vue3 通过重写 Map/Set 的方法实现拦截。以 Map 的 set 方法为例: 拦截逻辑 : 代理对象的 set 方法被调用时,先获取原始值(通过 toRaw 避免代理嵌套),执行原始 set 方法,随后触发依赖更新。 依赖收集 : 在 get 拦截中,若访问的是 size 或迭代方法(如 keys 、 values ),会调用 track 收集依赖: 3. size 属性的依赖追踪机制 size 是 Map/Set 的访问器属性,其值随数据增减而变化。Vue3 通过以下步骤确保其响应性: 访问追踪 : 当读取 size 时,触发 get 拦截,调用 track(target, 'iterate') ,将当前副作用函数(effect)关联到 'iterate' 依赖集合。 更新触发 : 当执行 set 、 add 、 delete 等修改数据的方法时,除了触发对应键的更新,还会额外触发 '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 在响应式系统中的行为与普通对象一致,同时兼顾了性能与准确性。