当前位置:首页 > 技术 > 正文内容

Vue 3.2.26 响应式核心:Effect 机制深度剖析

访客 技术 2026年6月29日 1

Vue 3 的响应式系统建立在 effect 这一核心抽象之上。本文通过源码视角,解析依赖收集与触发执行的完整链路。

Effect 实例的创建与运行

当调用 effect(fn) 时,内部会实例化一个响应式副作用对象:

const runner = new ReactiveEffect(fn)

// 立即执行以完成首次依赖收集
runner.run()

ReactiveEffectrun 方法实现了精密的栈管理机制:

run() {
  // 避免嵌套执行时的重复入栈
  if (!stack.includes(this)) {
    try {
      // 压栈并标记当前激活的 effect
      stack.push((currentEffect = this))
      openTracking()
      
      // 位运算标记层级深度
      trackBit = 1 << ++trackDepth
      
      // 根据深度选择优化策略
      if (trackDepth <= maxBits) {
        markDeps(this)      // 标记已有依赖
      } else {
        clearEffect(this)   // 深度过大时全量清理
      }
      
      return this.fn()      // 执行用户函数,触发依赖收集
    } finally {
      if (trackDepth <= maxBits) {
        sweepDeps(this)     // 清理失效依赖
      }
      
      trackBit = 1 << --trackDepth
      closeTracking()
      
      // 出栈并恢复上一个激活的 effect
      stack.pop()
      currentEffect = stack.length > 0 
        ? stack[stack.length - 1] 
        : undefined
    }
  }
}

依赖收集:track 的实现

当响应式数据被读取时,track 函数建立数据与 effect 的映射关系:

export function track(target, type, key) {
  // 仅在追踪状态下执行
  if (!shouldTrack()) return
  
  // 获取或创建 target -> depsMap 的映射
  let keyToDeps = proxyMap.get(target)
  if (!keyToDeps) {
    proxyMap.set(target, (keyToDeps = new Map()))
  }
  
  // 获取或创建 key -> dep 的映射
  let dep = keyToDeps.get(key)
  if (!dep) {
    keyToDeps.set(key, (dep = createDep()))
  }
  
  // 开发环境下携带调试信息
  const debugInfo = __DEV__ 
    ? { effect: currentEffect, target, type, key } 
    : undefined
  
  // 将当前 effect 加入依赖集合
  addSubscriber(dep, debugInfo)
}

数据结构呈现三级嵌套:WeakMap<Target, Map<Key, Set<Effect>>>,确保垃圾回收不受阻碍。

依赖触发:trigger 的分发策略

数据变更时,trigger 根据操作类型精准定位待执行的 effect:

export function trigger(target, type, key, newVal, oldVal) {
  const keyToDeps = proxyMap.get(target)
  if (!keyToDeps) return    // 从未被追踪,直接返回
  
  const effectsToRun = []
  
  // 操作类型分发
  if (type === TriggerTypes.CLEAR) {
    // 集合清空:触发所有依赖
    effectsToRun.push(...keyToDeps.values())
  } 
  else if (key === 'length' && isArray(target)) {
    // 数组长度变化:收集索引相关的依赖
    keyToDeps.forEach((dep, k) => {
      if (k === 'length' || k >= newVal) {
        effectsToRun.push(dep)
      }
    })
  } 
  else {
    // SET | ADD | DELETE 操作
    if (key !== undefined) {
      effectsToRun.push(keyToDeps.get(key))
    }
    
    // 迭代器依赖的特殊处理
    switch (type) {
      case TriggerTypes.ADD:
        if (!isArray(target)) {
          effectsToRun.push(keyToDeps.get(ITERATE_KEY))
          if (isMap(target)) {
            effectsToRun.push(keyToDeps.get(MAP_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // 新增数组索引触发 length 更新
          effectsToRun.push(keyToDeps.get('length'))
        }
        break
        
      case TriggerTypes.DELETE:
        if (!isArray(target)) {
          effectsToRun.push(keyToDeps.get(ITERATE_KEY))
          if (isMap(target)) {
            effectsToRun.push(keyToDeps.get(MAP_ITERATE_KEY))
          }
        }
        break
        
      case TriggerTypes.SET:
        if (isMap(target)) {
          effectsToRun.push(keyToDeps.get(ITERATE_KEY))
        }
        break
    }
  }
  
  // 去重并调度执行(实际源码包含 scheduler 逻辑)
  const effects = new Set(effectsToRun.flat())
  effects.forEach(effect => {
    if (effect !== currentEffect) {
      if (effect.scheduler) {
        effect.scheduler()
      } else {
        effect.run()
      }
    }
  })
}

关键设计要点

  • 栈结构:通过 effectStack 支持嵌套 effect 的正确关联
  • 位运算优化:利用 trackOpBiteffectTrackDepth 实现高效的依赖标记与清理
  • 迭代器键ITERATE_KEYMAP_KEY_ITERATE_KEY 处理 for...infor...of 等遍历场景
  • 数组特化:区分索引操作与长度变更的依赖关系
标签: Vue 3

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。