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]的多余节点删掉

最后更新于
这有帮助吗?