虚拟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
- 静态提升:创建静态节点时保存,后续直接复用
- 使用最长递增子序列优化了对比流程