Vuejs设计与实现 —— 渲染器核心 Diff 算法(下)

简介: Vuejs设计与实现 —— 渲染器核心 Diff 算法

快速 Diff 算法

Vue.js 3 借鉴了 iviinferno 这两个框架中使用的算法:快速 Diff 算法,这个算法的性能优于 Vue.js 2 中所采用的 双端 Diff 算法.

以下涉及的源码位置均在:vue-core-3.2.31-main\packages\runtime-core\src\renderer.ts 中的 patchKeyedChildren 函数中

节点预处理

对于 相同位置前置节点后置节点,由于它们在新旧两组子节点中的相对位置不变,因此并 不需要 进行 移动 操作,只需 进行 patch更新 即可.

处理前置节点

通过开启一个 while 循环,从前往后 依次遍历新旧两组子节点:

  • 若当前新旧节点 相同,则通过 patch 进行 更新
  • 若当前新旧节点 不同,则终止循环,即前置节点处理结束

Vue.js 3 中对应源码如下:

// 1. sync from start 处理前置节点
// (a b) c
// (a b) d e
while (i <= e1 && i <= e2) {
  const n1 = c1[i]
  const n2 = (c2[i] = optimized
    ? cloneIfMounted(c2[i] as VNode)
    : normalizeVNode(c2[i]))
  if (isSameVNodeType(n1, n2)) {
    patch(
      n1,
      n2,
      container,
      null,
      parentComponent,
      parentSuspense,
      isSVG,
      slotScopeIds,
      optimized
    )
  } else {
    break
  }
  i++
}
复制代码

处理后置节点

通过开启一个 while 循环,从后往前 依次遍历新旧两组子节点:

  • 若当前新旧节点 相同,则通过 patch 进行 更新
  • 若当前新旧节点 不同,则终止循环,即后置节点处理结束

Vue.js 3 中对应源码如下:

// 2. sync from end 处理后置节点
// a (b c)
// d e (b c)
while (i <= e1 && i <= e2) {
  const n1 = c1[e1]
  const n2 = (c2[e2] = optimized
    ? cloneIfMounted(c2[e2] as VNode)
    : normalizeVNode(c2[e2]))
  if (isSameVNodeType(n1, n2)) {
    patch(
      n1,
      n2,
      container,
      null,
      parentComponent,
      parentSuspense,
      isSVG,
      slotScopeIds,
      optimized
    )
  } else {
    break
  }
  e1--
  e2--
}
复制代码

处理剩余已知公共序列的节点

当完成 节点预处理 后,很可能出现以下两种情况,而这些剩余节点是很容易根据已处理过的前后节点推断出它们的具体位置的:

  • 旧节点遍历完成,新节点有剩余,此时意味着有新节点需要挂载,通过 patch 将剩余新节点依次进行 挂载
  • 新节点遍历完成,旧节点有剩余,此时意味着有旧节点需要卸载,通过 unmount 将剩余旧节点依次进行 卸载

image.png

Vue.js 3 中对应源码如下:

// 3. common sequence + mount
// (a b)
// (a b) c
// i = 2, e1 = 1, e2 = 2
// (a b)
// c (a b)
// i = 0, e1 = -1, e2 = 0
if (i > e1) { // 旧节点遍历完后
  if (i <= e2) { // 新节点还有剩余
    const nextPos = e2 + 1
    const anchor = nextPos < l2 ? (c2[nextPos] as VNode).el : parentAnchor
    while (i <= e2) {
      // 挂载操作
      patch(
        null,
        (c2[i] = optimized
          ? cloneIfMounted(c2[i] as VNode)
          : normalizeVNode(c2[i])),
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
      i++
    }
  }
}
// 4. common sequence + unmount
// (a b) c
// (a b)
// i = 2, e1 = 2, e2 = 1
// a (b c)
// (b c)
// i = 0, e1 = 0, e2 = -1
else if (i > e2) { // 新节点遍历完成
  while (i <= e1) { // 旧节点还有剩余
    // 卸载操作
    unmount(c1[i], parentComponent, parentSuspense, true)
    i++
  }
}
复制代码

处理剩余未知序列的节点

直接来看 vue.js 3 在源码中举的例子:

旧节点:  [i ... e1 + 1]   =>   a b [c d e] f g
新节点:  [i ... e2 + 1]   =>   a b [e d c h] f g
当前索引: i = 2,  e1 = 4,  e2 = 5
复制代码

其中,经过 节点预处理 后的剩余节点,即 [c d e][e d c h] 的部分是乱序的,针对这部分节点的处理是很关键的:

  • 通过 toBePatched 保存新节点的数量,即 toBePatched = e2 - s2 + 1
  • 基于 newChildren 的剩余节点,构造基一个形如 key: indexkeyToNewIndexMap 索引映射,本质是一个 Map 对象
  • 遍历旧的一组节点中剩余为处理的节点,进行 patch 更新或 unmount 卸载
  • 若当前遍历的 老节点的 key 能在 keyToNewIndexMap 中获取到对应的索引值,则说明当前节点是可复用的节点,可通过 patch 进行 更新,并通过 patched 记录下当前已 被更新/被复用 的节点数
  • 若当前遍历的 老节点的 key 不能在 keyToNewIndexMap 中获取到对应的索引值,则说明当前的老节点通过 unmount 进行卸载
  • patched >= toBePatched,则说明所有剩余的新节点都已经在剩余旧节点中找到并更新完成,此时需要对旧节点中剩余老节点通过 unmount 进行卸载
  • 若当前老节点对应新节点中的索引 小于 上一次记录的索引值,则说明当前节点需要移动,将 moved 变量标识为 true,便于后续基于 最长递增子序列 进行 移动 操作
  • 通过以上操作后,可以通过构造一个 最长的稳定子序列 用于后续节点的 移动 操作,即 最长递增子序列算法
  • 通过构建 newIndexToOldIndexMap 数组,用于存储 当前新节点老节点中 的索引值
  • 基于 newIndexToOldIndexMap 数组通过 getSequence(newIndexToOldIndexMap) 得到最长递增子序列,其中相关算法感兴趣的可自行研究
  • 从后往前 遍历,其中索引 i 指向新的一组子节点的最后一个节点,而索引 j 指向的是最长递增子序列中的最后一个元素
  • 若当前新节点对应老节点中的索引为 0,则说明当前节点需要进行 挂载
  • movedtrue 则说明当前新节点需要进行 移动

Vue.js 3 中对应源码如下:

// 5.3 move and mount
  // generate longest stable subsequence only when nodes have moved
  const increasingNewIndexSequence = moved
    ? getSequence(newIndexToOldIndexMap)
    : EMPTY_ARR
  j = increasingNewIndexSequence.length - 1
  // looping backwards so that we can use last patched node as anchor
  for (i = toBePatched - 1; i >= 0; i--) {
    const nextIndex = s2 + i
    const nextChild = c2[nextIndex] as VNode
    const anchor =
      nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor
    if (newIndexToOldIndexMap[i] === 0) {
      // mount new
      patch(
        null,
        nextChild,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    } else if (moved) {
      // move if:
      // There is no stable subsequence (e.g. a reverse)
      // OR current node is not among the stable sequence
      if (j < 0 || i !== increasingNewIndexSequence[j]) {
        move(nextChild, container, anchor, MoveType.REORDER)
      } else {
        j--
      }
    }
  }
复制代码

完结


目录
相关文章
|
14天前
|
JavaScript 前端开发 算法
vue渲染页面的原理
vue渲染页面的原理
|
2月前
|
敏捷开发 人工智能 JavaScript
Figma-Low-Code:快速将Figma设计转换为Vue.js应用,支持低代码渲染、数据绑定
Figma-Low-Code 是一个开源项目,能够直接将 Figma 设计转换为 Vue.js 应用程序,减少设计师与开发者之间的交接时间,支持低代码渲染和数据绑定。
151 3
Figma-Low-Code:快速将Figma设计转换为Vue.js应用,支持低代码渲染、数据绑定
|
4月前
|
监控 JavaScript 前端开发
Vue 异步渲染
【10月更文挑战第23天】Vue 异步渲染是提高应用性能和用户体验的重要手段。通过理解异步渲染的原理和优化策略,我们可以更好地利用 Vue 的优势,开发出高效、流畅的前端应用。同时,在实际开发中,要注意数据一致性、性能监控和调试等问题,确保应用的稳定性和可靠性。
|
5月前
|
算法 JavaScript UED
Diff 算法的实现原理
【10月更文挑战第18天】Diff 算法是 Vue.js 中实现高效 DOM 更新的核心机制,通过合理的比较和优化策略,能够在保证界面正确性的同时,最大程度地减少 DOM 操作,提高应用的性能和用户体验。
91 2
|
5月前
|
算法 JavaScript
Vue 中的 Diff 算法
【10月更文挑战第18天】需要注意的是,Diff 算法虽然能够提高性能,但在某些复杂的场景下,可能仍然会存在一些性能瓶颈。因此,在实际开发中,我们需要根据具体情况合理地使用 Diff 算法,并结合其他优化手段来提高应用的性能。
40 1
|
5月前
|
JavaScript 算法 前端开发
vue 中diff算法
【10月更文挑战第10天】
76 1
|
5月前
|
JavaScript 算法 前端开发
【VUE】Vue的diff算法和React的diff算法
【VUE】Vue的diff算法和React的diff算法
|
10天前
|
机器学习/深度学习 算法 数据安全/隐私保护
基于生物地理算法的MLP多层感知机优化matlab仿真
本程序基于生物地理算法(BBO)优化MLP多层感知机,通过MATLAB2022A实现随机数据点的趋势预测,并输出优化收敛曲线。BBO模拟物种在地理空间上的迁移、竞争与适应过程,以优化MLP的权重和偏置参数,提升预测性能。完整程序无水印,适用于机器学习和数据预测任务。
|
4天前
|
机器学习/深度学习 存储 算法
基于MobileNet深度学习网络的活体人脸识别检测算法matlab仿真
本内容主要介绍一种基于MobileNet深度学习网络的活体人脸识别检测技术及MQAM调制类型识别方法。完整程序运行效果无水印,需使用Matlab2022a版本。核心代码包含详细中文注释与操作视频。理论概述中提到,传统人脸识别易受非活体攻击影响,而MobileNet通过轻量化的深度可分离卷积结构,在保证准确性的同时提升检测效率。活体人脸与非活体在纹理和光照上存在显著差异,MobileNet可有效提取人脸高级特征,为无线通信领域提供先进的调制类型识别方案。
|
3天前
|
算法 安全 数据安全/隐私保护
基于BBO生物地理优化的三维路径规划算法MATLAB仿真
本程序基于BBO生物地理优化算法,实现三维空间路径规划的MATLAB仿真(测试版本:MATLAB2022A)。通过起点与终点坐标输入,算法可生成避障最优路径,并输出优化收敛曲线。BBO算法将路径视为栖息地,利用迁移和变异操作迭代寻优。适应度函数综合路径长度与障碍物距离,确保路径最短且安全。程序运行结果完整、无水印,适用于科研与教学场景。

热门文章

最新文章