美文网首页
diff 算法

diff 算法

作者: Cherry丶小丸子 | 来源:发表于2023-08-11 19:22 被阅读0次
diff 是什么

diff 就是比较两棵树,render 会生成两颗树,一棵新树 newVnode,一棵旧树 oldVnode,然后两棵树进行对比更新找差异就是 diff,全称 difference,在 vue 里面 diff 算法是通过 patch 函数来完成的,所以有的时候也叫 patch 算法

在 vue 实例调用 $mount 的时候,就已经把 updateComponent 方法通过 new Watcher(vm, updateComponent) 传入到渲染 watcher 里面, 且挂在 watcher.getter 上,得到一个渲染 watcher,渲染 watcher,在以后每次响应式数据更新都会执行 watcher.getter 即 updateComponent 方法

// lifecycle.js
updateComponent = () => {
    vm._update(vm._render(), hydrating)
}

new Watcher(vm, updateComponent);

当组件被创建和更新的时候, 
使用 _render 函数生成虚拟 dom 树,
使用 _update 函数,通过传入 _render 生成的新的虚拟 dom 树,通过当前组件的 _vnode 属性拿到 旧的虚拟 dom 树进行对比,
最终通过 vm.__patch__() 进行 diff 算法 完成对真实 dom 的更新

diff 算法主要有四块内容,分别是:

  • 创建节点
  • 删除节点
  • 更新节点
  • 更新子节点

在对比的过程,vue 采用同级比较、递归深度优先的方式进行比较,同级比较就是说它不会跨越结构进行比较,在判断两个节点是否相同的时候,是根据虚拟节点的 key 和 tag 来进行判断的

具体来说,首先对根节点进行对比,如果相同则将旧节点关联的真实 dom 的引用挂到新节点上,然后根据需要更新属性到真实 dom,然后再对比其子节点数组;如果不相同,则按照新节点的信息递归创建所有真实 dom,同时挂到对应虚拟节点上,然后移除掉旧的 dom。

在对比其子节点数组时,vue 对每个子节点数组使用了两个指针,分别指向头尾,然后不断向中间靠拢来进行对比,这样做的目的是尽量复用真实 dom,尽量少的销毁和创建真实 dom。如果发现相同,则进入和根节点一样的对比流程,如果发现不同,则移动真实 dom 到合适的位置。

patch 函数前两个参数位为 oldVnode 和 Vnode ,分别代表旧节点和新节点,主要做了四个判断
  • 如果没有新节点,有旧节点,直接触发旧节点的 destory 钩子
  • 如果没有旧节点,说明是页面刚开始初始化的时候,此时就不需要比较了,直接调用 createElm 新建元素
  • 如果新旧节点通过 sameVnode 判断是相同的,就调用 patchVnode 去处理这两个节点
  • 如果新旧节点通过 sameVnode 判断是不相同的,直接创建新节点,删除旧节点
patchVnode 主要做了两个判断
  • 新节点不是文本节点,是元素节点
    ** 新旧节点如果都有子节点,则调用 updateChildren 处理比较更新子节点
    ** 只有新节点有子节点,旧节点没有,那么不用比较了,直接全部新建节点并且添加进父节点
    ** 只有旧节点有子节点,新节点没有,那么也不用比较了,要做的就是把所有的旧节点删除
  • 新节点是文本节点,则直接更新 dom 的文本内容为新节点的文本内容
  • updateChildren 主要做了以下操作:
    1、设置新旧 VNode 的首尾指针
    2、新旧首尾指针进行比较,循环向中间靠拢,根据情况调用 patchVnode 进行 patch 重复流程、调用 createElem 创建一个新节点,从映射 map 找 key 一致的 VNode 节点再分情况操作
updateChildren 中的 while 循环

oldStartVnode 旧首节点、oldEndVnode 旧尾节点、newStartVnode 新首节点、newEndVnode 新尾节点
oldStartIdx 旧首指针、oldEndIdx 旧尾指针、newStartIdx 新首指针、newEndIdx 新尾指针

更新子节点的优化策略1:目标 old 节点的位置预测
  • 如果 oldStartVnode 和 newStartVnode 是同一个节点,直接 patchVnode,同时 oldStartIdx、newStartIdx 索引都加 1(向右移动)
  • 如果 oldEndVnode 和 newEndVnode 是同一个节点,直接 patchVnode,同时 oldEndIdx、newEndIdx 索引都减 1(向左移动)
  • 如果 oldStartVnode 和 newEndVnode 是同一个节点,直接 patchVnode,这时将 oldStartVnode.eml 移动到 oldEndVnode.elm 之后,同时 oldStartIdx 索引加1(向右移动)、newEndIdx 索引都减 1(向左移动)
  • 如果 oldEndVnode 和 newStartVnode 是同一个节点,直接 patchVnode,这时将 oldEndVnode.eml 移动到 oldStartVnode.elm 之后,同时 oldEndIdx 索引减1(向左移动)、newStartIdx 索引都加 1(向右移动)
更新子节点的优化策略2:key
  • 如果上面的四种情况都不是,就需要通过遍历 oldCh,拿到每个节点的 key 和 index, 就是 oldKeyToIdx: { key1: 0, key2: 1, key3: 2 } 这种情况,然后通过 newStartVnode 的 key 在 oldKeyToIdx 中查找同名 key 来获取索引,如果没匹配到索引就进行创建新节点,如果匹配到,再次判断两个节点是否是相同节点,如果相同就进行递归 patchVnode 流程,如果不同,就创建一个新的节点

当循环结束,此时要么是旧数组相交,要么是新数组相交,只有这两种情况:
旧数组相交,说明新数组还没有相交,那么要根据相交的位置插入新数组剩余的未遍历到节点
新数组相交,说明旧数组还没有相交,那么要删除旧数组剩余的未遍历到的节点
至此 diff 流程结束

https://www.jianshu.com/p/e502002e1895
https://mp.weixin.qq.com/s/haOUo3EWcu40rVdeeEU5Zg

https://blog.csdn.net/f18855666661/article/details/119512912

相关文章

网友评论

      本文标题:diff 算法

      本文链接:https://www.haomeiwen.com/subject/enkzpdtx.html