Diff算法

VUE 中的 Diff 实现

Diff 算法比较只会进行同层进行,不会进行跨层比较

相同节点判断

function sameVnode (a, b) {
  return (
    a.key === b.key &&  // key值
    a.tag === b.tag &&  // 标签名
    a.isComment === b.isComment &&  // 是否为注释节点
    // 是否都定义了data,data包含一些具体信息,例如onclick , style
    isDef(a.data) === isDef(b.data) &&  
    sameInputType(a, b) // 当标签是<input>的时候,type必须相同
  )
}

首先比较newVnode 和oldVnode 的sel 属性(VDOM的构建描述’dev’, ’ul.className’, ’li#idName’ 等),如果节点不同说明Vnode被完全改变了,直接替换为newVnode 因为Diff算法为同层比较,所以不需要进行children的比较

节点合并

function patchVnode(oldVnode, newVnode){
  const el = newVnode.el = oldVnode.el //合并引用
  let i, oldCh = oldVnode.children, ch = newVnode.children
  if (oldVnode === newVnode) return //如果严格一致直接返回
  if (oldVnode.text !== null && newVnode.text !== null && oldVnode.text !== newVnode.text) { // 文本节点处理
    api.setTextContent(el, newVnode.text)
  }else{
    updateEle(el, newVnode, oldVnode) //更新节点
    if (oldCh && ch && oldCh !== ch) {
          updateChildren(el, oldCh, ch)
    }else if (ch){
          createEle(newVnode) 
    }else if (oldCh){
          api.removeChildren(el)
    }
  }
}
  • 找到对应的真实dom,称为el

  • 判断newVnode和oldVnode是否指向同一个对象,如果是,那么直接return

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

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

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

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

子节点比较

有子节点 —>无子节点 无子节点 —>有子节点 子节点—>文本节点 文本节点—>子节点 这几种情况都比较简单,关键点在于有子节点—>有子节点这种情况的比较 为了方便理解可以虚拟成两个数组进行比较

  • 所有操作其实都是在老列表上进行,整个列表会被分割为已经操作过的节点和未操作的节点两部分

  • 操作过的节点组成最后结果列表,未操作过的列表继续作为旧列表进行匹配操作

  • 之后语境里的头尾都是根据双指针标识出来的待处理段的头尾

  • 判断相等后的两项代表已处理完,指针要移动相应的进行移动,头处理则开始指针后移,尾处理则结束指针前移

  • 判断相等也要执行patchVnode操作合并

  • 头头相等,合并后插入旧表头,指针都后移

  • 尾尾相等,合并后插入旧表尾,指针都前移

  • 老头等于新尾,则把老头移到最后

  • 老尾等于新头,则吧老尾移到最头

  • 头尾都没有相等则以新头进行哈希表比较

  • 没有哈希表要以旧列表key值构建

  • 查到不到则直接新头插入旧表最前,然后新表开始指针后移

  • 查到了,也要进行一下全等判断,确定相等后,把对应的节点移到旧表最开始,并且相应位置节点赋值为undefined.然后新表开始指针后移

  • 为啥要再次进行全等判断呢,因为key值命中一次后,哈希表不会更新,key可能再次命中,而节点已经被移走,还有些key值重复等特殊情况

  • 结束条件

  • oldS > oldE表示oldChidlren先遍历完,那么就将多余的vCh根据index添加到dom中去

  • S > E表示newChidlren先遍历完,那么就在真实dom中将区间为[oldS, oldE]的多余节点删掉

最后更新于