Vue diff原理
灵感来源
Vitrual DOM与真实DOM
真实DOM
<div>
    <p>123</p>
</div>Virtual DOM(伪代码)
var Vnode = {
  tag: "div",
  children: [{ tag: "p", text: "123" }]
};详解
仅在同层比较,不会跨层级比较

流程图

代码部分
patch 
function patch(oldVnode, vnode) {
  // some code
  if (sameVnode(oldVnode, vnode)) {
    patchVnode(oldVnode, vnode);
  } else {
    const oEl = oldVnode.el; // 当前oldVnode对应的真实元素节点
    let parentEle = api.parentNode(oEl); // 父元素
    createEle(vnode); // 根据Vnode生成新元素
    if (parentEle !== null) {
      api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl)); // 将新元素添加进父元素
      api.removeChild(parentEle, oldVnode.el); // 移除以前的旧元素节点
      oldVnode = null;
    }
  }
  // some code
  return vnode;
}patch函数主要进行了两个分支流程
- 判断两节点是否是一样的,如果是则执行 - patchVnode
- 不一样则用 - Vnode替换- oldVnode
patchVnode
patchVnode (oldVnode, vnode) {
    const el = vnode.el = oldVnode.el
    let i, oldCh = oldVnode.children, ch = vnode.children
    if (oldVnode === vnode) return
    if (oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text) {
        api.setTextContent(el, vnode.text)
    }else {
        updateEle(el, vnode, oldVnode)
    	if (oldCh && ch && oldCh !== ch) {
            updateChildren(el, oldCh, ch)
    	}else if (ch){
            createEle(vnode) //create el's children dom
    	}else if (oldCh){
            api.removeChildren(el)
    	}
    }
}关键流程
- 找到对应的真实 dom,称为 - el
- 判断 - Vnode和- oldVnode是否指向同一个对象,如果是,那么直接- return
- 如果他们都有文本节点并且不相等,那么将 - el的文本节点设置为- Vnode的文本节点
- 如果 - oldVnode有子节点而- Vnode没有,则删除- el的子节点
- 如果 - oldVnode没有子节点而- Vnode有,则将- Vnode的子节点真实化之后添加到- el
- 如果两者都有子节点,则执行 - updateChildren函数比较子节点,这一步很重要
updateChildern(核心)

我们将它们取出来并分别用 s 和 e 指针指向它们的头 child 和尾 child

现在分别对 oldS、oldE、S、E 两两做 sameVnode 比较,有四种比较方式,当其中两个能匹配上那么真实 dom 中的相应节点会移到 Vnode 相应的位置,这句话有点绕,打个比方
- 如果是 - oldS和- E匹配上了,那么真实- dom中的第一个节点会移到最后
- 如果是 - oldE和- S匹配上了,那么真实- dom中的最后一个节点会移到最前,匹配上的两个指针向中间移动
- 如果四种匹配没有一对是成功的,分为两种情况 - 如果新旧子节点都存在 - key,那么会根据- oldChild的- key生成一张- hash表,用- S的- key与- hash表做匹配,匹配成功就判断- S和匹配节点是否为- sameNode,如果是,就在真实- dom中将成功的节点移到最前面,否则,将- S生成对应的节点插入到- dom中对应的- oldS位置,- oldS和- S指针向中间移动。
- 如果没有 - key,则直接将- S生成新的节点插入真实- DOM(ps:这下可以解释为什么- v-for的时候需要设置- key了,如果没有- key那么就只会做四种匹配,就算指针中间有可复用的节点都不能被复用了)
 
再配个图(假设下图中的所有节点都是有 key 的,且 key 为自身的值)

- 1.第一步 
oldS = a, oldE = d;S = a, E = b;oldS 和 S 匹配,则将 dom 中的 a 节点放到第一个,已经是第一个了就不管了,此时 dom 的位置为:a b d
- 2. 第二步 
oldS = b, oldE = d;S = c, E = boldS 和 E 匹配,就将原本的 b 节点移动到最后,因为 E 是最后一个节点,他们位置要一致,这就是上面说的:当其中两个能匹配上那么真实 dom 中的相应节点会移到 Vnode 相应的位置,此时 dom 的位置为:a d b
- 3. 第三步 
oldE 和 E 匹配,位置不变此时 dom 的位置为:a d b
- 4. 第四步 
遍历结束,说明 oldCh 先遍历完。就将剩余的 vCh 节点根据自己的的 index 插入到真实 dom 中去,此时 dom 位置为:a c d b
这个匹配过程的结束有两个条件:
- oldS > oldE表示- oldCh先遍历完,那么就将多余的- vCh根据- index添加到- dom中去(如上图)
- S > E表示- vCh先遍历完,那么就在真实- dom中将区间为- [oldS, oldE]的多余节点删掉

最后更新于
这有帮助吗?

