4. diff算法

1/17/2023 virtual-dom

# 什么是 Diff 算法

diff 算法就是用来计算出 Virtual Dom 中被改变的部分。因为Vue React 框架都是 只用改变状态类影响视图自动更新,因此当数据状发生变化时候要计算出 对应的最小的变化的部分,而不是重新渲染政整个页面,以此达到节约性能的目的。

梳理一下整个DOM-diff的过程:

  • 用JS对象模拟DOM(虚拟DOM)
  • 把此虚拟DOM转成真实DOM并插入页面中(render)
  • 如果有事件发生修改了虚拟DOM,比较两棵虚拟DOM树的差异,得到差异对象(diff)
  • 把差异对象应用到真正的DOM树上(patch)

# 虚拟DOM

diff 算法就是用来计算出 Virtual Dom 中被改变的部分。因为Vue React 框架都是 只用改变状态类影响视图自动更新,因此当数据状发生变化时候要计算出对应的最小的变化的部分,而不是重新渲染政整个页面,以此达到节约性能的目的。

# updateChildren整体过程

# diff算法类似排序算法:

  • 查找两棵树每一个节点的差异。(第一棵树的每个节点去和第二棵树的每一个节点去对比,若有n个节点,比较n²次。找到差异,在进行一次循环,更新差异的部分。)
    传统做法

# Snabbdom根据DOM的特点对传统的diff算法做了优化

  • DOM操作时候很少会跨级别操作节点
  • 只比较同级别的节点(若有n个节点,则比较n次) diff算法优化

# 执行过程

对开始和结束节点比较的时候,总共有四种情况:

  • oldStartVnode/newStartVnode(旧开始节点/新开始节点)
  • oldEndVnode/newEndVnode(旧结束节点/新结束节点)
  • oldStartVnode/newEndVnode(旧开始节点/新结束节点)
  • oldEndVnode/newStartVnode(旧结束节点/新开始节点) 对比开始和结束节点

# 开始节点和结束节点

如果新旧开始节点是sameVnode(key和sel相同)

  • 调用patchVnode()对比和更新节点
  • 把旧开始和新开始索引往后移动 oldStartIdx++ / newStartIdx++ 开始节点和结束节点 如果新旧开始节点不是sameVnode,就从最后一个节点开始比较,如果最后一个节点是sameVnode,移动索引,开始比较倒数第二个节点

如果是sameVnode,复用旧节点对应的DOM元素。在patchVnode中会对比新旧vnode的差异,把变化的部分更新到复用的旧节点元素上。如果是文本、子元素发生变化,不需要创建DOM,不会进行DOM操作,以此来提高性能。

# 旧开始节点和新结束节点

  • 调用patchVnode()对比和更新节点
  • 把oldStartVnode对应的DOM元素,移动到右边,更新索引 旧开始节点和新结束节点

# 旧结束节点和新开始节点

  • 调用patchVnode()对比和更新节点
  • 把oldStartVnode对应的DOM元素,移动到左边,更新索引 旧结束节点和新开始节点

# 循环结束

  • 当老节点的所有子节点先遍历完(oldStartIdx > oldEndIdx),循环结束
  • 新节点的所有子节点先遍历完(newStartIdx > newEndIdx),循环结束

oldStartIdx > oldEndIdx

  • 如果老节点的数组先遍历完(oldStartIdx > oldEndIdx)
  • 说明新节点有剩余,把剩余节点批量插入到右边 oldStartIdx > oldEndIdx

newStartIdx > newEndIdx

  • 如果新节点的数组先遍历完(newStartIdx > newEndIdx)
  • 说明老节点有剩余,把剩余节点批量删除 newStartIdx > newEndIdx

# 总结

  • 使用 Virtual DOM,在首次渲染的时候会影响性能因为要创建额外的对象来描述真实 DOM,在更新少量标签的时候也不会有性能上的提升,在 DOM 结构复杂的时候更新 DOM,会有性能的提升,因为仅仅会把前后两次 DOM 树的差异,更新到真实 DOM。
  • VNode 的 key 属性是可选的,当需要的时候给它赋值
  • 给 VNode 设置 key 之后,当在对元素列表排序,或者给列表项插入项的时候会重用上一次对应的 DOM 对象,减少渲染次数,因此会提高性能
  • Snabbdom 中的 patch 函数是通过 Snabbdom 的入口函数 init 生成的,init 中初始化模块和 DOM 操作的 api,最终返回 patch,这里的 init 是一个高阶函数,在 init 函数内部缓存了两个参数,在返回的 patch 函数中可以通过闭包访问到 init 中初始化的模块和 DOM 操作的 api
  • patch 函数接收新旧两个 VNode 作为参数,其中第一个参数即可以是 DOM 对象又可以是 VNode 对象,如果第一个参数是 DOM 对象的话,patch 内部会先把 DOM 对象转换成 VNode,再和第二个参数中的新的 VNode 对比,然后更新 DOM
  • patchVnode 的作用是对比新旧两个 VNode 节点本身,如果两个节点都没有 text 属性再去对比他们的子节点,如果有 text 属性并且新旧 VNode 的 text 不相等,这时候把新节点的 text 属性更新到 DOM 上
  • updateChildren 函数的作用是对比新旧 VNode 节点的子节点,在对比的过程中因为 DOM 操作的特殊性,同时也为了优化操作,所以只对比两颗树中的同一层级的子节点。
  • createElm 函数的作用是把 VNode 转换成 DOM 对象,但是没有把新创建的 DOM 挂载到 DOM 树,而是先存储到当前 VNode 对象的 elm 属性中,并返回新创建的 DOM 对象
最近更新时间: 2/4/2023, 10:01:08 AM
강남역 4번 출구
Plastic / Fallin` Dild