转载声明:文章来源:https://blog.csdn.net/qq_42072086/article/details/107965196
一、diff算法的时间复杂度
两个树的完全的 diff 算法是一个时间复杂度为 O(n3) , Vue 进行了优化·O(n3) 复杂度的问题转换成 O(n) 复杂度的问题(只比较同级不考虑跨级问题),因为在前端操作dom的时候了,不会把当前元素作为上一级元素或下一级元素,很少会跨越层级地移动Dom元素,常见的都是同级的比较。 所 以 Virtual Dom只会对同一个层级的元素进行对比。
二、vue中diff算法的原理
在数据发生变化,vue是先根据真实DOM生成一颗 virtual DOM ,当 virtual DOM 某个节点的数据改变后会生成一个新的 Vnode ,然后 Vnode 和 oldVnode 作对比,发现有不一样的地方就直接修改在真实的DOM上,然后使 oldVnode 的值为 Vnode ,来实现更新节点。
1.原理简述:
(1)先去同级比较,然后再去比较子节点
(2)先去判断一方有子节点一方没有子节点的情况
(3)比较都有子节点的情况
(4)递归比较子节点
2.源码:
源码文件地址:src/core/vdom/patch.js
源码总结:
在进行节点比较时,是通过patch这个方法实现,其中如果不是真实元素并且用sameVnode看两个是否是同一个元素,如果是然后会调用patchVnode进行比较,比较的是虚拟节点不是真实节点,如果不值得去比较则用 Vnode 替换 oldVnode。patchVnode会比较这两个节点,判断 Vnode 和 oldVnode 是否指向同一个对象,如果是,那么直接 return,复用老的真是元素。如果不是会用新的子节点和旧的字节做比较。如果他们都有文本节点并且不相等,那么将el的文本节点设置为Vnode的文本节点;如果两个都有子节点的情况下会调用updateChildren方法比较他们的子节点;只有新的节点有子节点而旧节点没有子节点,就会把新增的子节点插入到旧的dom中;如果当前新的节点中没有子节点,旧的中有子节点,然后就会把旧的节点删除掉;
updateChildren方法中,先定义了四对指针,先比较新的和旧的第一个子节点是否一样,如果一样会移动前面两个指针,以此类推,如果比对最后新的节点多了一个子节点就把它插入旧的中。如果开始第一个子节点不一样就从后面开始进行比较(从尾子节点开始比较),比到最后把新增的节点插入到旧的dom中。还有这种情况就是头和尾节点都不相等的情况下,用当前的头和旧的尾节点进行比较,如果一样就把旧的尾节点移到前面去,然后将旧的尾指针向前移动一位,当前头指针移动到下一个子节点上。另一种就是用旧的结尾和新的开头节点进行比较,四种比较策略。如果存在key,就会拿的当前子节点的key去旧的子节点中找,如果找到就将它移动到旧节点的前面,然后就将指针移向当前节点的第二个子节点上,以此类推,如果当前子节点没有就将它直接插入到旧的节点中,最后把旧的节点中多余出的子节点删除掉。
(1)patch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | return function patch (oldVnode, vnode, hydrating, removeOnly) { if (isUndef(vnode)) { if (isDef(oldVnode)) invokeDestroyHook(oldVnode) return } let isInitialPatch = false const insertedVnodeQueue = [] if (isUndef(oldVnode)) { // empty mount (likely as component), create new root element isInitialPatch = true createElm(vnode, insertedVnodeQueue) } else { const isRealElement = isDef(oldVnode.nodeType) // 如果不是真实元素并且两个是同一个元素的话,然后会调用patchVnode进行比较 // 比较是虚拟节点不是真实节点 if (!isRealElement && sameVnode(oldVnode, vnode)) { // patch existing root node patchVnode(oldVnode, vnode, insertedVnodeQueue, null , null , removeOnly) } else { if (isRealElement) { // mounting to a real element // check if this is server-rendered content and if we can perform // a successful hydration. if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) { oldVnode.removeAttribute(SSR_ATTR) hydrating = true } if (isTrue(hydrating)) { if (hydrate(oldVnode, vnode, insertedVnodeQueue)) { invokeInsertHook(vnode, insertedVnodeQueue, true ) return oldVnode } else if (process.env.NODE_ENV !== 'production' ) { warn( 'The client-side rendered virtual DOM tree is not matching ' + 'server-rendered content. This is likely caused by incorrect ' + 'HTML markup, for example nesting block-level elements inside ' + '<p>, or missing <tbody>. Bailing hydration and performing ' + 'full client-side render.' ) } } // either not server-rendered, or hydration failed. // create an empty node and replace it oldVnode = emptyNodeAt(oldVnode) } // replacing existing element const oldElm = oldVnode.elm const parentElm = nodeOps.parentNode(oldElm) // create new node createElm( vnode, insertedVnodeQueue, // extremely rare edge case: do not insert if old element is in a // leaving transition. Only happens when combining transition + // keep-alive + HOCs. (#4590) oldElm._leaveCb ? null : parentElm, nodeOps.nextSibling(oldElm) ) // update parent placeholder node element, recursively if (isDef(vnode.parent)) { let ancestor = vnode.parent const patchable = isPatchable(vnode) while (ancestor) { for (let i = 0; i < cbs.destroy.length; ++i) { cbs.destroy[i](ancestor) } ancestor.elm = vnode.elm if (patchable) { for (let i = 0; i < cbs.create.length; ++i) { cbs.create[i](emptyNode, ancestor) } // #6513 // invoke insert hooks that may have been merged by create hooks. // e.g. for directives that uses the "inserted" hook. const insert = ancestor.data.hook.insert if (insert.merged) { // start at index 1 to avoid re-invoking component mounted hook for (let i = 1; i < insert.fns.length; i++) { insert.fns[i]() } } } else { registerRef(ancestor) } ancestor = ancestor.parent } } // destroy old node if (isDef(parentElm)) { removeVnodes([oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode) } } } invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) return vnode.elm } |
(2)patchVnode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | function patchVnode ( oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly ) { if (oldVnode === vnode) { // 如果相同直接return return } if (isDef(vnode.elm) && isDef(ownerArray)) { // clone reused vnode vnode = ownerArray[index] = cloneVNode(vnode) } const elm = vnode.elm = oldVnode.elm if (isTrue(oldVnode.isAsyncPlaceholder)) { if (isDef(vnode.asyncFactory.resolved)) { hydrate(oldVnode.elm, vnode, insertedVnodeQueue) } else { vnode.isAsyncPlaceholder = true } return } // reuse element for static trees. // note we only do this if the vnode is cloned - // if the new node is not cloned it means the render functions have been // reset by the hot-reload-api and we need to do a proper re-render. 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.update[i](oldVnode, vnode) 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, insertedVnodeQueue, removeOnly) } else if (isDef(ch)) { // 如果只有新的有子节点 if (process.env.NODE_ENV !== 'production' ) { checkDuplicateKeys(ch) // 就把新增的子节点插入到旧的节点中 } if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '' ) addVnodes(elm, null , ch, 0, ch.length - 1, insertedVnodeQueue) } else if (isDef(oldCh)) { // 如果只有老的有子节点 removeVnodes(oldCh, 0, oldCh.length - 1) // 就会把旧的节点直接删除 } else if (isDef(oldVnode.text)) { // 如果旧节点有子文本节点 nodeOps.setTextContent(elm, '' ) // 就把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) } } |
(3)updateChildren(双指针)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, 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, insertedVnodeQueue, newCh, newStartIdx) oldStartVnode = oldCh[++oldStartIdx] newStartVnode = newCh[++newStartIdx] } else if (sameVnode(oldEndVnode, newEndVnode)) { patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx) oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, 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, newStartVnode, insertedVnodeQueue, newCh, 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, oldCh, oldStartIdx, oldEndIdx) if (isUndef(idxInOld)) { // New element // 如果找不到直接进行插入 createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false , newCh, newStartIdx) } else { vnodeToMove = oldCh[idxInOld] // 如果找的到就看他是否相等,如果相等就直接进行移动 if (sameVnode(vnodeToMove, newStartVnode)) { patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx) oldCh[idxInOld] = undefined canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm) } else { // same key but different element. treat as new element // 如果索引相同,但元素不同,会重新创建一个插入到旧的节点中 createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false , newCh, newStartIdx) } } newStartVnode = newCh[++newStartIdx] } } if (oldStartIdx > oldEndIdx) { refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) } else if (newStartIdx > newEndIdx) { removeVnodes(oldCh, oldStartIdx, oldEndIdx) } } |
图:
帖子还没人回复快来抢沙发