虚拟dom和diff算法

183 阅读3分钟

虚拟dom(virtiual node)

用js对象来模拟DOM结构。把每一个标签都转为一个对象,对象有三个属性:tag(必选)、props、children。

{ 
   tag:'div',
   props:{
     id:'one'
     },
   children:[
     { 
   tagName:'p',
   props:{
     id:'tow'
     },
   children:[
     'hi'
   ]
}
   ]
}

为什么有虚拟dom

  • 实际应用中会频繁操作dom
  • 操作dom节点的成本很高
  • 单纯操作js的成本很低
  • 原生dom有非常多属性和事件。在dom发生变化的时候,通过diff算法和虚拟dom,只对变化的dom进行操作,而不是更新整个视图,性能高于直接操作dom。
    Vue里虚拟dom的数据更新机制采用异步更新队列,把变更后的数据装入数据更新的异步队列(patch),用来做新老vnode对比。

diff算法

在vue中叫patch,其核心参考了Snabbdom,通过新旧虚拟dom对比(patch过程),找出最小变化的地方转为进行dom操作。
页面首次渲染时会调用patch并创建新的vnode,不进行更深层次的比较。

diff对比流程

  • 组件中数据发生变化时,会触发setter,通过Dep.notify通知Watcher,对应的Watcher会通知更新并执行更新函数,它会执行render函数获取新的虚拟dom,执行patch对比上次渲染结果的老的虚拟dom,并计算出最小的变化,然后再去根据这个最小变化去更新真实的dom。 diff算法:深度优先,同层比较策略,计算出最小变化。

diff算法优化

vue2的diff算法,复杂度O(n^3)最小编辑距离 vue3的diff算法,复杂度O(n)

  • 只比较同一层级,不跨级比较(深度优先算法,时间复杂度O(n))
  • 比较标签名
  • 比较key(标签名和key相同认为是相同节点)

key的作用

  • 更高效地更新虚拟dom,因为它可以非常精确得找到相同节点,因此patch过程会非常高效
  • vue在patch过程中会判断两个节点是不是相同节点,key是一个必要条件。
  • 应该避免使用数组下标作为key,因为key值不是唯一的话,会导致相同的节点进行patchNode更新文本。
  • vue 判断两个节点是否相同时主要判断两者的元素类型和key等,如果不设置key,就可能认为这两个是相同节点,只能做更新操作,造成大量不必要的dom更新操作。

vue3的优化

  • 事件缓存:将事件缓存变成静态
  • 添加静态标记:Vue2是全量diff,vue3是静态标记+非全量diff
  • 静态提升:创建静态节点时保存,后续直接复用
  • 使用最长递增子序列优化了对比流程

参考资料