虚拟DOM Diff算法

141 阅读4分钟

虚拟DOM

对虚拟Dom理解

虚拟DOM是一个用来表示真实DOM的JS对象

可以将多次DOM修改的结果一次性更新到页面上,从而有效减少页面渲染的次数,减少修改DOM的重绘重排次数,提高渲染性能

虚拟dom真的比真实dom性能好吗

首次渲染大量的DOM时,由于多了一层虚拟DOM的计算,确实是要比直接插入innerHTML慢

但是他能保证性能下限,在执行DOM操作的时候进行针对性的优化时,还是更快的

Diff算法原理

Diff算法是一种对比算法, 对比旧虚拟DOM和新虚拟DOM, 对比哪个是虚拟节点改了

找出这个虚拟节点,并只更新这个虚拟节点对于的真实节点,而不用更新其他数据没发生改变的点,实现精准地更新真实DOM,进而提高效率

diff的比较只会在同层级进行,不会跨层级比较,所以diff算法是深度优先算法 事件复杂度是O(n)

Diff对比流程

当数据改变时,会触发setter,并通过Dep.notify去通知所有的订阅者Watcher,订阅者们就会调用 patch方法,给真实的DOM打补丁,更新相应的试图

patch方法

patch方法的作用就是调用sameVnode方法对比当前同层的虚拟节点是否为同一种类型的标签

是的话就继续执行patchVnode方法进行深层对比

不是的话就没必要对比了,直接整个节点替换成新虚拟节点

patchVnode

假如是同一种类型的标签时,往下执行patchVnode方法,会做以下事情:

1.找到对应的真实DOM,称为el

2.判断newVNode和oldVNode是否指向同一个对象,如果是,直接就return了,如果不是继续往下执行

3.如果他们都有文本节点且不相等,那么将el的文本节点设置为newVnode的文本节点

4.如果oldVnode有子节点而newVnode没有,则删除el的子节点

5.如果oldVnode没有子节点而newVnode有,则将newVnode的子节点真实化后添加到el

6.如果两者都有子节点,则执行updateChildren函数比较子节点

function patchVnode(oldVnode, newVnode) {
  const el = newVnode.el = oldVnode.el // 获取真实DOM对象
  // 获取新旧虚拟节点的子节点数组
  const oldCh = oldVnode.children, newCh = newVnode.children
  // 如果新旧虚拟节点是同一个对象,则终止
  if (oldVnode === newVnode) return
  // 如果新旧虚拟节点是文本节点,且文本不一样
  if (oldVnode.text !== null && newVnode.text !== null && oldVnode.text !== newVnode.text) {
    // 则直接将真实DOM中文本更新为新虚拟节点的文本
    api.setTextContent(el, newVnode.text)
  } else {
    // 否则

    if (oldCh && newCh && oldCh !== newCh) {
      // 新旧虚拟节点都有子节点,且子节点不一样

      // 对比子节点,并更新
      updateChildren(el, oldCh, newCh)
    } else if (newCh) {
      // 新虚拟节点有子节点,旧虚拟节点没有

      // 创建新虚拟节点的子节点,并更新到真实DOM上去
      createEle(newVnode)
    } else if (oldCh) {
      // 旧虚拟节点有子节点,新虚拟节点没有

      //直接删除真实DOM里对应的子节点
      api.removeChild(el)
    }
  }
}

updateChildren

updateChildren方法使用首尾指针法

这个比较难描述,简单来说就是通过两个指针对新旧两个Vnode列表进行比对,旧Vnode列表复用与新Vnode列表相同的Vnode节点,同时改变真实DOM旧Vnode节点位置,使与新VNode节点的位置保持一致。如果旧Vnode列表的Vnode多了,就将其全部删掉,如果少了,就按照新Vnode的位置插入到真实DOM对应的位置上

详细描述参见

juejin.cn/post/699495…

Vue中key作用

在进行子节点的 diff算法 过程中,会进行 旧首节点和新首节点的sameNode对比,而key是否相同就是判定新旧两个节点是否为同一类型节点的其中一个标准

为什么不能用index做key

假如本来有a,b,c三个小li,然后在将新的小li,d插在第一的位置,用index来作为key的话,节点的key永远都不会变,所以新旧Vnode列表进行比对的时候,由于新旧列表的第一个到第三元素的key相同,所以都进行了patchVnode更新文本,而因为之前没有key为4的节点,c节点反而变成了新增的节点。所以diff算法就完全起不到优化性能的作用了