Vue中diff算法的理解
<div class="root" name="root">
<p>1</p>
<div>11</div>
</div>
{
type: "tag",tagName: "div",attr: {
className: "root"
name: "root"
},parent: null,children: [{
type: "tag",tagName: "p",attr: {},parent: {} /* 父节点的引用 */,children: [{
type: "text",tagName: "text",content: "1"
}]
},{
type: "tag",content: "11"
}]
}]
}
时间复杂度
diff策略
- 两个不同类型的元素将产生不同的树。
- 通过渲染器附带
key
属性,开发者可以示意哪些子元素可能是稳定的。
- 只进行统一层级的比较,如果跨层级的移动则视为创建和删除操作。
- 如果是不同类型的元素,则认为是创建了新的元素,而不会递归比较他们的孩子。
- 如果是列表元素等比较相似的内容,可以通过
key
来唯一确定是移动还是创建或删除操作。
- 此节点被添加或移除
->
添加或移除新的节点。
- 属性被改变
->
旧属性改为新属性。
- 文本内容被改变
->
旧内容改为新内容。
- 节点
tag
或key
是否改变->
改变则移除后创建新元素。
分析
// line 714
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode,vnode)) {
// patch existing root node
patchVnode(oldVnode,vnode,insertedVnodeQueue,null,removeOnly)
}else{
// ...
}
// line 35
function sameVnode (a,b) {
return (
a.key === b.key && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a,b)
) || (
isTrue(a.isAsyncPlaceholder) &&
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
)
}
-
key
必须相同,如果都是undefined
则也是相同的。
-
DOM
元素的标签必须相同。
// line 501
function patchVnode (oldVnode,removeOnly) {
// ...
if (isDef(data) && isPatchable(vnode)) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode,vnode)
if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode,vnode)
}
//...
}
- 如果孩子不是
textNode
,那么需要再分三种情况处理。
- 如果当前孩子是
textNode
那么直接更新text
即可。
- 有新孩子无旧孩子,直接创建新的。
- 有旧孩子无新孩子,直接删除旧的。
- 新旧孩子都有,那么调用
updateChildren
。
if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) updateChildren(elm,oldCh,ch,removeOnly)
} else if (isDef(ch)) {
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(ch)
}
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm,'')
addVnodes(elm,ch.length - 1,insertedVnodeQueue)
} else if (isDef(oldCh)) {
removeVnodes(oldCh,oldCh.length - 1)
} else if (isDef(oldVnode.text)) {
nodeOps.setTextContent(elm,'')
}
} else if (oldVnode.text !== vnode.text) {
nodeOps.setTextContent(elm,vnode.text)
}
// line 404
function updateChildren (parentElm,newCh,removeOnly) {
let oldStartIdx = 0
let 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,idxInOld,vnodeToMove,refElm
// removeOnly is a special flag used only by <transition-group>
// to ensure removed elements stay in correct relative positions
// during leaving transitions
const canMove = !removeOnly
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(newCh)
}
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
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,newStartIdx)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode,newEndVnode)) {
patchVnode(oldEndVnode,newEndVnode,newEndIdx)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode,newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode,newEndIdx)
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,newStartIdx)
canMove && nodeOps.insertBefore(parentElm,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,newStartIdx)
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(oldCh,oldEndIdx)
}
}
old VNode: a(oldStartIdx) b c d e f(oldEndIdx)
new VNode: b(newStartIdx) f g(newEndIdx)
DOM Node: a b c d e f
old VNode: a(oldStartIdx) undefined c d e f(oldEndIdx)
new VNode: b f(newStartIdx) g(newEndIdx)
DOM Node: b a c d e f
old VNode: a(oldStartIdx) undefined c d e(oldEndIdx) f
new VNode: b f g(newStartIdx)(newEndIdx)
DOM Node: b f a c d e
old VNode: a(oldStartIdx) undefined c d e(oldEndIdx) f
new VNode: b f g(newEndIdx) (newStartIdx)
DOM Node: b f g a c d e
- 如果
oldStartldx > oldEndldx
,说明老节点遍历完成了,新的节点比较多,所以多出 来的这些新节点,需要创建出来并添加到真实DOM Node
后面。
- 如果
newStartldx >newEndldx
,说明新节点遍历完成了,老的节点比较多,所以多 出来的这些老节点,需要从真实DOM Node
中删除这些节点。
old VNode: a(oldStartIdx) undefined c d e(oldEndIdx) f
new VNode: b f g(newEndIdx) (newStartIdx)
DOM Node: b f g
https://github.com/WindrunnerMax/EveryDay
https://github.com/aooy/blog/issues/2
https://www.zhihu.com/question/66851503
https://juejin.im/post/6844903607913938951
https://juejin.im/post/6844903592483094535
https://reactjs.org/docs/reconciliation.html
https://www.cnblogs.com/lilicat/p/13448827.html
https://www.cnblogs.com/lilicat/p/13448915.html
https://github.com/lihongxun945/myblog/issues/33
https://www.cnblogs.com/xujiazheng/p/12101764.html
https://blog.csdn.net/dongcehao/article/details/106987886
https://blog.csdn.net/qq2276031/article/details/106407647
https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/151
https://leetcode-cn.com/problems/edit-distance/solution/bian-ji-ju-chi-by-leetcode-solution/