从零到一打造 Vue3 响应式系统 Day 10 - 为何 Effect 会被指数级触发?

DOM 交互

我们的响应式系统经过前几天的努力,已经初具雏形,感觉可以加入一些 DOM 交互,来进行简单的测试。

HTML 复制代码
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
    <style>
      body {
        padding: 150px;
      }
    </style>
  </head>
  <body>
    <button id="btn">按钮</button>
    <script type="module">
      import { ref, effect } from '../dist/reactivity.esm.js'

      const flag = ref(true)

      effect(() => {
        flag.value
        console.count('effect')
      })

      btn.onclick = () => {
        flag.value = !flag.value
      }
    </script>
  </body>
</html>

我们预期每次点击按钮,effect 只会执行一次。但实际情况看起来不太妙。

console.count 的结果可以看到,effect 的执行次数随着点击呈现指数级增长。这肯定是不行的。

我们来了解一下问题的症结所在。

执行步骤图解

初始化页面

页面加载时,effect 执行一次。在执行过程中,读取了 flag.value,触发 getter 进行依赖收集。 系统会创建一个 link1 节点,将 effectflag 关联起来。到这里都符合预期。

第一次点击按钮

当按钮第一次被点击,flag.valuetrue 变为 false,触发了 setter。 setter 内的 propagate 函数开始遍历 flag 的依赖链表。

propagate 执行 link1 中存储的 effect.run()

effect 函数重新执行,又读取了 flag.value,再次触发了 getter。

此时问题出现了 :在 effect.run() 的过程中,又进行了一次依赖收集,系统创建了一个新的 link2 节点并添加到链表尾部。

执行结束后的链表:

第二次点击按钮

当按钮又被点击,flag.valuefalse 变为 true,再次触发 setter。

propagate 开始遍历依赖链表。但这一次,链表上有两个节点 (link1link2)。

  1. propagate 先执行 link1 中的 effect.run()effect 内部读取 flag.value,触发依赖收集,创建了一个新的 link3 节点并添加到链表尾部。
  2. propagate 接着执行 link2 中的 effect.run()effect 内部又一次读取 flag.value,触发依赖收集,又创建了一个新的 link4 节点并添加到链表尾部。

执行结束后的链表:

执行完成后的链表结构

我们可以发现在触发更新时,链表上的每一个节点 都会触发一次 effect 的重新执行,而每一次执行又会创建 一个新的节点加入到链表中,因此发生了指数级触发 effect 的情况。

关键问题点

每次 effect 重新执行时:

  1. 没有检查该 effect 是否已经存在于依赖链表中。
  2. 盲目地创建新的 Link 节点并添加到链表末尾。
  3. 导致依赖链表在每次更新时都会成倍增长。

因此,每次点击按钮,链表上的每一个 Link 都会触发一次 effect 的重新执行,而在每一次执行中又会创建新的 Link,从而导致重复执行和指数级增长现象。

因为下个篇幅比较长,今天就先讲到这里。大家需要先理解问题的症结所在,这样明天在实现解决方案时,才能明白我们为什么要那样做。


想了解更多 Vue 的相关知识,抖音、B站搜索我师父「远方os」,一起跟日安当同学。

相关推荐
小四的小六11 分钟前
WebView到AI产品,最值得掌握的3个核心能力——从自己产品里总结的能力地图
前端·ai编程·webview
前端逗比逗36 分钟前
Electron 全维度完整配置手册(最新稳定版,适配 Electron 25+)
前端·electron
xiaobaoyu40 分钟前
聊天问答文字逐步显示实现
前端
随风一样自由1 小时前
【前端+项目分析】`img` vs `Image`:从两个真实项目看前端图片组件的正确选择
前端·image·img·项目对比分析
倾颜1 小时前
断线之后,不要重跑 AI:在 POST + NDJSON 中实现可恢复 Agent 流
前端·后端·agent
程序员黑豆1 小时前
鸿蒙应用开发之父子组件传参:@Prop 装饰器使用教程
前端·后端·harmonyos
信也科技布道师1 小时前
从绝对定位到可维护页面:一次 MasterGo 还原链路的实战复盘
前端
程序员黑豆1 小时前
鸿蒙应用开发之V2状态管理:@Local、@ObservedV2、@Trace 使用教程
前端·后端·harmonyos
锻炼²2 小时前
Edge 地址栏搜索被其他浏览器劫持
前端·edge
Slice_cy2 小时前
Mint 自研框架设计与实现:从重复开发走向配置驱动(三)
前端·后端·架构