推断对应节点是不是有必要举行比较(sameVnode)
function sameVnode(oldVnode, vnode){
return vnode.key === oldVnode.key && vnode.sel === oldVnode.sel
}
- 假如值得比较会实行patchVnode(oldVnode, vnode)
- 假如不值得比较,新节点直接把老节点全部替换了
打补丁(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)
}
}
}
节点的比较有5种状况
-
if (oldVnode === vnode)
,他们的援用一致,能够认为没有变化。 -
if(oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text)
,文本节点的比较,须要修正,则会挪用Node.textContent = vnode.text。 -
if( oldCh && ch && oldCh !== ch )
, 两个节点都有子节点,而且它们不一样,如许我们会挪用updateChildren函数比较子节点,这是diff的中心。 -
else if (ch)
,只要新的节点有子节点,挪用createEle(vnode),vnode.el已援用了老的dom节点,createEle函数会在老dom节点上增加子节点。 -
else if (oldCh)
,新节点没有子节点,老节点有子节点,直接删除老节点。
更新子节点(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
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)) {
patchVnode(oldStartVnode, newStartVnode)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
}else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
}else if (sameVnode(oldStartVnode, newEndVnode)) {
patchVnode(oldStartVnode, newEndVnode)
api.insertBefore(parentElm, oldStartVnode.el, api.nextSibling(oldEndVnode.el))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
}else if (sameVnode(oldEndVnode, newStartVnode)) {
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) {
before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].el
addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx)
}else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}
重要的思绪也许就是,定义变量,离别纪录当前比较的新、旧节点中的首尾索引与节点(oldStartVnode、oldEndVnode、newStartVnode、newEndVnode)(后续称为比较区间)
(图片来自:https://github.com/aooy/blog/… )
经由过程对 oldStartVnode、oldEndVnode、newStartVnode、newEndVnode 做,两两的 sameVnode 比较
比较推断有4种,按递次顺次比较
oldStartVnode —— newStartVnode (头仇人)
oldEndVnode —— newEndVnode (尾对尾)
oldStartVnode —— newEndVnode (头对尾)
oldEndVnode —— newStartVnode (尾仇人)
假如个中一种比较建立了,那末oldVode中的响应节点会 以当前比较区间为基准 移到newVnode响应的位置上,
然后比较区间会依据当前的比较前提范例,以头或尾为减少比较区间的方向,减少区间
比方: 当oldStartVnode,newEndVnode值得比较时, 将oldStartVnode.el移动到oldEndVnode.el后边
当4种比较都不建立时,会运用key去比较,并在终究都使newVode的比较区间,头部 减1
当oldVnode的key列表中能匹配到对应的key时,推断比较节点的选择器属性是不是一样
不一样则直接在当前比较区间的头部,
新创建
一个newVnode的Dom插进去比较节点中的oldVnode无需处置惩罚,由于背面的比较中不会有建立的比较前提,终究会直接删除节点
- 假如一样则将比较节点中的oldVnode移动到当前比较区间的头部(所认为节点设置key能够更高效的应用dom),并将比较区间中oldVnode底本的索引位置赋值为Null
假如终究key列表也没能匹配到的话,也是直接在当前比较区间的头部,新创建
一个newVnode的Dom插进去。
末了在完毕时会存在2种状况
oldStartIdx > oldEndIdx
oldVnode先遍历完,证实newVode有新增的节点,或许一致,这类状况会将newVode盈余的节点插进去到oldVnode比较区间的末端
newStartIdx > newEndIdx
这时候是newVode先遍历完,证实newVode里删除了某些节点,此时oldVnode的比较区间节点是已不存在的,会将他们删除。
参考文章:
剖析vue2.0的diff算法