2024-03-09·12 min read

DOM Diff 阅读笔记

DOM Diff 算法原理的阅读笔记

React算法

https://github.com/ascoders/weekly/blob/master/前沿技术/190.精读《DOM diff 原理详解》.md

1、明确 Dom diff的目的

  • 是为了找到如何从 old 变成 new 的操作路径,而不是有什么变化。
  • 操作路径不一定是最小路径,因为是算法算的,也要考虑找到最小路线的成本
  • 在 jquery 时代,等于是人工找到最小修改路径,但是认为这个跟业务逻辑其实不耦合,实际操作起来,对业务代码来说,心智成本过高

2、Vue Dom diff

  • 按层比对
  • 主要就是双指针两头收
  • 中间部分先创建一个 map
    • 然后在新的部分比对 map 的变化,是改变位置,还是删除
  • 把添加和删除的动作做完,最后做移动的动作
  • 最后按照最长递增子序列的方式,来找最优的改变位置的序列

3、React Dom diff

  • 同样是按层比对的,按层比对其实是基于浏览器 DOM 树本身的设计,如果变化的剧烈,按层比对都不是更优的解法,那显然已经都跟切换成一个新网站差不多了。我们默认基于的假设是,dom diff 的幅度时同一块区域,和小范围的
  • 仅右移策略:对元素发生的位置变化,只会将其移动到右边,如果右边移动完了,其他位置也就有序了
  • 遍历 old 储存 map
  • 最后移动位置,按照只向右移动来做。

4.AI的补充:

当然,我可以给你一个具体的例子来说明这个假设。

假设我们有一个列表组件,用于显示一个水果列表。初始状态下,水果列表如下:

jsx

<ul> <li _key_``="apple">苹果</li> <li _key_``="banana">香蕉</li> <li _key_``="orange">橙子</li></ul>

现在,我们要更新这个水果列表,将"香蕉"移动到"橙子"的后面,同时添加一个新的水果"西瓜":

jsx

<ul> <li _key_``="apple">苹果</li> <li _key_``="orange">橙子</li> <li _key_``="banana">香蕉</li> <li _key_``="watermelon">西瓜</li></ul>

按照 React 的 DOM Diff 算法中的"仅向右移动"策略,更新过程如下:

  1. React 为新的子节点列表建立 map:

    txt

    { "apple": <li key="apple">苹果</li>, "orange": <li key="orange">橙子</li>, "banana": <li key="banana">香蕉</li>, "watermelon": <li key="watermelon">西瓜</li>}

  2. React 遍历旧的子节点列表:

    • 对于 <li key="apple">苹果</li>,在新的 map 中找到了对应的 key,因此 React 会保留该节点。
    • 对于 <li key="banana">香蕉</li>,在新的 map 中找到了对应的 key,但是位置比之前更靠右,因此 React 会将该节点向右移动到正确的位置。
    • 对于 <li key="orange">橙子</li>,在新的 map 中找到了对应的 key,因此 React 会保留该节点。
  3. 对于新的子节点列表中剩余的节点 <li key="watermelon">西瓜</li>,React 会创建一个新的节点并插入到 DOM 中。

最终更新后的 DOM 结构如下:

jsx

<ul> <li _key_``="apple">苹果</li> <li _key_``="orange">橙子</li> <li _key_``="banana">香蕉</li> <li _key_``="watermelon">西瓜</li></ul>

在这个例子中,"仅向右移动"的策略可以减少 DOM 操作的次数。React 只需要移动"香蕉"节点,并创建一个新的"西瓜"节点,而不需要对其他节点进行多余的操作。

这个例子体现了假设的合理性:在实际应用中,列表的更新通常是局部的,新的子节点列表与旧的子节点列表的顺序相似。"仅向右移动"的策略可以很好地处理这种情况,提高性能。

当然,如果列表的顺序发生了大规模的变化,例如完全反转,"仅向右移动"的策略可能会导致不必要的 DOM 操作。但是在大多数实际应用中,这种情况较为少见。

  1. 原来精读的总结也非常精确,我这里直接拷贝
    1. 完全对比 O(n³) 无法接受,故降级为同层对比的 O(n) 方案。
    2. 为什么降级可行?因为跨层级很少发生,可以忽略。
    3. 同层级也不简单,难点是如何高效位移,即最小步数完成位移。
    4. Vue 为了尽量不移动,先左右夹击跳过不变的,再找到最长连续子串保持不动,移动其他元素。
    5. React 采用仅右移方案,在大部分从左往右移的业务场景中,得到了较好的性能。