vue-diff

VDom & Dom

  • DOM
<div>
<p>tadm</p>
</div>
  • Virtual DOM

    {
    tag: "div",
    children:{
    { tag: 'p', text: 'tadm' }
    }
    }

Diff

DFS,同层级进行比较

  • 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
}
  • sameVnode

    function sameVnode(a, b){
    return {
    a.key === b.key && // key 值(v-for)
    a.tag === b.tag && // 标签
    a.isComment === b.isComment && // 注释
    isDef(a.data) === isDef(b.data) && // 定义了 data(onclick, style, ...)
    sameInputType(a, b) // input 时 type 也需要相同
    }
    }
  • patchVnode

patchVnode (oldVnode, vnode) {
const el = vnode.el = oldVnode.el
let i, oldCh = oldVnode.children, ch = vnode.children
// 相等则直接返回退出
if (oldVnode === vnode) return
// 如果都有文本节点且不等则将 el 的文本节点设置为 vnode's
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){
// old 无子节点而 vnode 有则直接创建
createEle(vnode) //create el's children dom
}else if (oldCh){
// old 有而 vnode 无则删除子节点
api.removeChildren(el)
}
}
}
  • updateChildren
updateChildren (parentElm, oldCh, newCh) {
let oldStartIdx = 0, newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx
let idxInOld
let elmToMove
let before
// old or new 遍历完则结束
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (oldStartVnode == null) { // 对于vnode.key的比较,会把oldVnode = null
oldStartVnode = oldCh[++oldStartIdx]
}else if (oldEndVnode == null) {
oldEndVnode = oldCh[--oldEndIdx]
}else if (newStartVnode == null) {
newStartVnode = newCh[++newStartIdx]
}else if (newEndVnode == null) {
newEndVnode = newCh[--newEndIdx]
}else if (sameVnode(oldStartVnode, newStartVnode)) {
// oldS & S 值得比较则 patchVnode 且均向后走一步
patchVnode(oldStartVnode, newStartVnode)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
}else if (sameVnode(oldEndVnode, newEndVnode)) {
// oldE & E 值得比较则 patchVnode 且均向前走一步
patchVnode(oldEndVnode, newEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
}else if (sameVnode(oldStartVnode, newEndVnode)) {
// oldS & E 值得比较则 patchVnode 且 oldS 移到末尾
// oldS 前进一步, E 后退一步
patchVnode(oldStartVnode, newEndVnode)
api.insertBefore(parentElm, oldStartVnode.el, api.nextSibling(oldEndVnode.el))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
}else if (sameVnode(oldEndVnode, newStartVnode)) {
// oldE & S 值得比较则 patchVnode 且 oldE 移到开头
// oldE 前进一步, S 后退一步
patchVnode(oldEndVnode, newStartVnode)
api.insertBefore(parentElm, oldEndVnode.el, oldStartVnode.el)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
}else {
// 使用key时的比较
if (oldKeyToIdx === undefined) {
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // 有key生成index表
}
idxInOld = oldKeyToIdx[newStartVnode.key]
if (!idxInOld) {
api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)
newStartVnode = newCh[++newStartIdx]
}
else {
elmToMove = oldCh[idxInOld]
if (elmToMove.sel !== newStartVnode.sel) {
api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)
}else {
patchVnode(elmToMove, newStartVnode)
oldCh[idxInOld] = null
api.insertBefore(parentElm, elmToMove.el, oldStartVnode.el)
}
newStartVnode = newCh[++newStartIdx]
}
}
}
if (oldStartIdx > oldEndIdx) {
// 说明 oldCh 先遍历完 则将剩下的 vCh 按照 index add
// Example:
// a b d --> a c d b ---> add c
before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].el
addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx)
}else if (newStartIdx > newEndIdx) {
// 说明 vCh 先遍历完 则将中间 [oldS, oldE] 的这些节点 remove
// Example:
// b a d f e --> a b e ---> remove d, f
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}

参考:详解vue的diff算法

Author: 𝓣𝓪𝓭𝓶
Link: https://liuhongwei3.github.io/2020/09/18/vue-diff/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.