简单diff算法 图文详解

577 阅读1分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

目的

找到可复用节点,然后移动到该去的位置。

参数:

  • n1 旧VNode
  • n2 新VNode
  • container 挂载点

思路

  • 新节点组遍历旧节点组,如果旧节点的索引 < lastIndex(最大索引),将其移动到最后,反之更新 lastIndex
  • 若在旧节点组中没有找到新节点,则在上一个节点之后插入
  • 遍历旧节点组,查找是否有不存在新节点组中的节点,卸载

流程图

Image [2].png

  • 遍历新节点 创建 lastIndex
  • 在旧的节点中找到 P3 lastIndex 赋值为旧节点中 P3 的 index

Image [3].png

  • 在旧的找到 新的P2 key === 旧的P2 key
  • 旧的P2 的 索引 < lastIndex 移动 P2

image.png

  • 在旧的中找到 新的P1 key === 旧的P1 key
  • 旧的P1 的 索引 < lastIndex 移动 P1

Image [5].png

简单diff算法步骤

  • oldChildren 定义为旧节点n1的children
  • newChildren 定义为新节点n2的children
  • 定义 lastIndex 为0 ,oldChildren 中找到 key 相同的最大索引值
  • 循环 newChildren,定义i为当前索引
    1. 定义 newVNode 为当前节点
    2. 定义 find 为是否在 oldChildren 中找到相同 key 节点
    3. 循环 oldChildren ,定义 j 是当前索引
      • 定义 oldVNode 为当前节点
      • patch( n1 , n2 , container )
      • j < lastIndex ?
        • 需要移动元素,找到 newVNode 的下一个节点 prevVNode
          • 有-> 
            • 定义 anchorprevVNode 对应真实DOM的下一个DOM
            • insert ( newVNode.e l,container,anchor )
          • 无-> 说明是第一个元素,无需操作
      • lastIndex 赋值为 j
      • 结束该循环
    4. find 为假?
      • 定义 anchornull(要插入的位置)
      • 找到 newVNode 的上一个节点( oldChildren[ i -1 ] ), 作为prevVNode
        • 有-> anchorprevVNode 对应真实DOM的下一个DOM
        • 无-> anchor 作为 containerfirstChild
      • patch(null,newVNode,container,anchor)挂载新的节点
  • 循环 oldChildren
    1. 定义 oldVNode
    2. 定义 has 为 newChildren 中是否有当前 oldVNodekey
    3. has 假,说明 newChildren 中没有该节点
    4. 卸载该节点

总结

  • key 相当于虚拟节点的身份证,通过比较 key 来找到可复用的节点
  • 简单diff算法的核心逻辑:拿新的节点去在旧的节点中找可复用的节点,找到了则记录改节点位置的索引(最大索引)。在此过程中,如果旧节点的索引小于最大索引,说明该节点对应的真实DOM需要移动
  • 添加节点:如果没有在旧节点中找到,则在该新节点的上一个DOM的后一个兄弟DOM前添加
  • 删除节点:在遍历完成后,再次遍历旧节点,如果没有在新节点中找到对应节点,则卸载该节点。

可以看到这种方式不是最优的解法 所以还有 双端diff算法