源码:https://github.com/vuejs/vue/blob/dev/src/core/vdom/patch.js
虚拟dom
diff算法首先要明确一个概念就是diff的对象是虚拟dom,更新真实dom则是diff算法的结果
Vnode基类
这个部分的代码 主要是为了更好地知道在diff算法中具体diff的属性的含义,当然也可以更好地了解vnode实例
整体过程
核心函数是patch函数
- isUndef判断(是不是undefined或者null)
- // empty mount (likely as component),create new root elementcreateElm(vnode,insertedVnodeQueue) 这里可以发现创建节点不是一个一个插入,而是放入一个队列中统一批处理
- 核心函数sameVnode
这里是一个外层的比较函数,直接去比较了两个节点的key,tag(标签),data的比较(注意这里的data指的是VNodeData),input的话直接比较type。
这会确认两个节点是否有进一步比较的价值,不然直接替换
替换的过程主要是一个createElm函数 另外则是销毁oldVNode
插入过程简化来说就是判断node的type分别调用
createComponent(会判断是否有children然后递归调用)
createComment
createTextNode
创建后使用insert函数
之后需要用hydrate函数将虚拟dom和真是dom进行映射
核心函数
if (isTrue(oldVnode.isAsyncPlaceholder)) {
if (isDef(vnode.asyncFactory.resolved)) {
hydrate(oldVnode.elm,insertedVnodeQueue)
} else {
vnode.isAsyncPlaceholder = true
}
return
}
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance
return
}
let i
const data = vnode.data
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode,vnode)
}
const oldCh = oldVnode.children
const ch = vnode.children
if (isDef(data) && isPatchable(vnode)) {
for (i = 0; i < cbs.update.length; ++i) cbs.updatei
if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode,vnode)
}
if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) updateChildren(elm,oldCh,ch,removeOnly)
} else if (isDef(ch)) {
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm,'')
addVnodes(elm,null,ch.length - 1,insertedVnodeQueue)
} else if (isDef(oldCh)) {
removeVnodes(elm,oldCh.length - 1)
} else if (isDef(oldVnode.text)) {
nodeOps.setTextContent(elm,'')
}
} else if (oldVnode.text !== vnode.text) {
nodeOps.setTextContent(elm,vnode.text)
}
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode,vnode)
}
}
const el = vnode.el = oldVnode.el 这是很重要的一步,让vnode.el引用到现在的真实dom,当el修改时,vnode.el会同步变化。
- 比较二者引用是否一致
- 之后asyncFactory不知道是做什么的,所以这个比较看不懂
- 静态节点比较key,相同后也不做重新渲染,直接拷贝componentInstance(once命令在此生效)
- 如果vnode是文本节点或注释节点,但是vnode.text != oldVnode.text时,只需要更新vnode.elm的文本内容就可以
- children的比较
- 如果只有oldVnode有子节点,那就把这些节点都删除
- 如果只有vnode有子节点,那就创建这些子节点,这里如果oldVnode是个文本节点就把vnode.elm的文本设置为空字符串
- 都有则updateChildren,这个之后详述
- 如果oldVnode和vnode都没有子节点,但是oldVnode是文本节点或注释节点,就把vnode.elm的文本设置为空字符串
updateChildren
这部分重点还是关注整个算法
首先四个指针,oldStart,oldEnd,newStart,newEnd,两个数组,oldVnode,Vnode。
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode,newStartVnode)) {
patchVnode(oldStartVnode,newStartVnode,insertedVnodeQueue)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode,newEndVnode)) {
patchVnode(oldEndVnode,newEndVnode,insertedVnodeQueue)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode,newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode,insertedVnodeQueue)
canMove && nodeOps.insertBefore(parentElm,oldStartVnode.elm,nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode,newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode,oldEndVnode.elm,oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh,oldStartIdx,oldEndIdx)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode,oldEndIdx)
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode,parentElm,false,newStartIdx)
} else {
vnodeToMove = oldCh[idxInOld]
if (sameVnode(vnodeToMove,newStartVnode)) {
patchVnode(vnodeToMove,insertedVnodeQueue)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm,vnodeToMove.elm,oldStartVnode.elm)
} else {
// same key but different element. treat as new element
createElm(newStartVnode,newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm,refElm,newStartIdx,newEndIdx,insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm,oldEndIdx)
}
}
一个循环比较的几种情况和处理(以下的++ --均指index的++ --)比较则是比较的node节点,简略写法 不严谨 比较用的是sameVnode函数也不是真的全等
整体循环不结束的条件oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx
- oldStart === newStart,oldStart++ newStart++
- oldEnd === newEnd,oldEnd-- newEnd--
- oldStart === newEnd,oldStart插到队伍末尾 oldStart++ newEnd--
- oldEnd === newStart,oldEnd插到队伍开头 oldEnd-- newStart++
- 剩下的所有情况都走这个处理简单的说也就两种处理,处理后newStart++
- newStart在old中发现一样的那么将这个移动到oldStart前
- 没有发现一样的那么创建一个放到oldStart之前
循环结束后并没有完成
还有一段判断才算完
简单的说就是循环结束后,看四个指针中间的内容,old数组中和new数组中,多退少补而已
总结
整体认识还很粗糙,不过以目前的水平和对vue的了解也就只能到这了
原文链接:https://www.f2er.com/vue/32957.html