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--
      }
    }
  }
复制代码

完结


目录
相关文章
|
1月前
|
监控 JavaScript 前端开发
Vue 异步渲染
【10月更文挑战第23天】Vue 异步渲染是提高应用性能和用户体验的重要手段。通过理解异步渲染的原理和优化策略,我们可以更好地利用 Vue 的优势,开发出高效、流畅的前端应用。同时,在实际开发中,要注意数据一致性、性能监控和调试等问题,确保应用的稳定性和可靠性。
|
2月前
|
算法 JavaScript UED
Diff 算法的实现原理
【10月更文挑战第18天】Diff 算法是 Vue.js 中实现高效 DOM 更新的核心机制,通过合理的比较和优化策略,能够在保证界面正确性的同时,最大程度地减少 DOM 操作,提高应用的性能和用户体验。
36 2
|
2月前
|
算法 JavaScript
Vue 中的 Diff 算法
【10月更文挑战第18天】需要注意的是,Diff 算法虽然能够提高性能,但在某些复杂的场景下,可能仍然会存在一些性能瓶颈。因此,在实际开发中,我们需要根据具体情况合理地使用 Diff 算法,并结合其他优化手段来提高应用的性能。
19 1
|
2月前
|
JavaScript 算法 前端开发
vue 中diff算法
【10月更文挑战第10天】
40 1
|
2月前
|
JavaScript 算法 前端开发
【VUE】Vue的diff算法和React的diff算法
【VUE】Vue的diff算法和React的diff算法
|
25天前
|
JavaScript API 开发者
Vue是如何进行组件化的
Vue是如何进行组件化的
|
27天前
|
JavaScript 前端开发 开发者
vue 数据驱动视图
总之,Vue 数据驱动视图是一种先进的理念和技术,它为前端开发带来了巨大的便利和优势。通过理解和应用这一特性,开发者能够构建出更加动态、高效、用户体验良好的前端应用。在不断发展的前端领域中,数据驱动视图将继续发挥重要作用,推动着应用界面的不断创新和进化。
|
1天前
|
JavaScript 关系型数据库 MySQL
基于VUE的校园二手交易平台系统设计与实现毕业设计论文模板
基于Vue的校园二手交易平台是一款专为校园用户设计的在线交易系统,提供简洁高效、安全可靠的二手商品买卖环境。平台利用Vue框架的响应式数据绑定和组件化特性,实现用户友好的界面,方便商品浏览、发布与管理。该系统采用Node.js、MySQL及B/S架构,确保稳定性和多功能模块设计,涵盖管理员和用户功能模块,促进物品循环使用,降低开销,提升环保意识,助力绿色校园文化建设。
|
28天前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱前端的大一学生,专注于JavaScript与Vue,正向全栈进发。博客分享Vue学习心得、命令式与声明式编程对比、列表展示及计数器案例等。关注我,持续更新中!🎉🎉🎉
32 1
vue学习第一章
|
28天前
|
JavaScript 前端开发 索引
vue学习第三章
欢迎来到瑞雨溪的博客,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中的v-bind指令,包括基本使用、动态绑定class及style等,希望能为你的前端学习之路提供帮助。持续关注,更多精彩内容即将呈现!🎉🎉🎉
26 1
vue学习第三章