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. 性能优化策略

  • 惰性代理:只有在访问时才创建嵌套对象的响应式代理
  • 避免重复触发:在 addset 操作前检查键是否已存在
  • 精确依赖:只为实际被访问的属性建立依赖关系
  • 批量更新:通过调度器实现多个操作的批量更新

这种设计确保了 Map/Set 的响应式处理既准确又高效,能够精确追踪 size 属性的依赖关系,在集合大小发生变化时正确触发组件更新。

Vue3 的响应式系统源码级 Map/Set 的响应式处理与 size 属性追踪原理 知识点描述 Map 和 Set 是 JavaScript 中常用的数据结构,Vue3 的响应式系统需要特殊处理这两种集合类型。本知识点将深入分析 Vue3 如何实现对 Map 和 Set 的响应式代理,特别是 size 属性的依赖收集和更新触发机制。 解题过程 1. 基础代理创建 2. size 属性的特殊处理原理 问题分析 :Map 的 size 属性是一个访问器属性,每次访问都会返回当前集合的大小 依赖收集 :当在 effect 中访问 map.size 时,需要建立 size 属性与当前 effect 的依赖关系 更新触发 :当执行 set.add() 、 map.set() 等改变大小的操作时,需要触发所有依赖 size 的 effect 3. 操作方法的重写与优化 Vue3 不会简单包装所有方法,而是针对性地处理会改变集合大小的方法: 4. 迭代方法的响应式处理 对于 keys() 、 values() 、 entries() 等迭代方法,也需要特殊处理: 5. 完整的依赖收集与触发机制 6. 性能优化策略 惰性代理 :只有在访问时才创建嵌套对象的响应式代理 避免重复触发 :在 add 和 set 操作前检查键是否已存在 精确依赖 :只为实际被访问的属性建立依赖关系 批量更新 :通过调度器实现多个操作的批量更新 这种设计确保了 Map/Set 的响应式处理既准确又高效,能够精确追踪 size 属性的依赖关系,在集合大小发生变化时正确触发组件更新。