current position:Home>Vue diff algorithm

Vue diff algorithm

2022-04-29 07:19:19Searching

vue Medium diff Algorithm concept

Although everyone who clicked in should know diff What is the algorithm , However, according to the process, we still need to briefly say , According to my personal understanding ,Vue Of diff The algorithm is based on the new and old virtual DOM Make a layer by layer comparison and update the real DOM

diff The algorithm only compares the nodes in the same layer
adopt dom Of key、tag、input-type And so on (sameVnode
Let's compare the two sides first , Re cross comparison , Then compare it in a disorderly way

vue in diff Implementation of algorithm

Tool function sameVnode

Before the start , You need to briefly understand this function ↓↓↓↓↓↓, The function works through tag Wait for the content to judge two vnode Are they the same? , The function is in diff It plays an important role in the judgment of the algorithm

/** *  Function function : adopt tag Wait for the content to judge two vnode Are they the same?  *  details : Through two values key Contrast , If key identical , Continue through the label 、isComment、data、input Judge whether the type is the same , It also determines the of asynchronous components asyncFactory Are they the same?  **/
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)
      )
    )
  )
}

Make a preliminary judgment according to whether the nodes are the same :patch function

This function can be said to be diff The entry function of the algorithm , When data changes ,defineProperty => get Would call Dep Of notify Method call Watcher updated , When every time I walk get call _update When , Will go patch function , Update reality DOM

Calling patch Function time , Will bring the old and the new vnode All in oldVnodeVnode( Regardless of server rendering )

When there is no new node : Represents that the component was deleted , call invokeDestroyHook Uninstall components
No old nodes : Represents a new component to be created , There is no need to compare the algorithm directly createElm Create new components
There are old nodes , There are new nodes : adopt sameVnode Compare components

Old node 、 The new node comparison is true: call patchVnode Compare and update
Old node 、 The new node comparison is false: Discard old nodes , call createElm Generate a new

function patch(oldVnode, vnode, hydrating, removeOnly) {
    
    //  without vnode, But there are oldVnode, Then call destroy hook   Uninstall components 
    if (isUndef(vnode)) {
    
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []

    //  If there are no old nodes   Represents a new component , Create directly 
    if (isUndef(oldVnode)) {
    
      // empty mount (likely as component), create new root element
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue)
    } else {
    
      // isRealElement:
      // nodeType It's true. DOM A property on , If nodeType There is , It means that this is a real node 
      //  When Vue Render or execute for the first time $moudnt(el) when , Enter the function for the first time oldVnode Namely el
      const isRealElement = isDef(oldVnode.nodeType)
      //  function sameVnode:  Judge whether the old and new nodes are the same 
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
    
        //  The old and new nodes are the same , go patchVnode
        patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
      } else {
    
          //  Server rendering related code ...
          oldVnode = emptyNodeAt(oldVnode)

        /** *  Down here is the difference between old and new nodes ( to update ) Operation process  */
        //  Get old node oldVnode The corresponding parent is true DOM node 
        const oldElm = oldVnode.elm
        const parentElm = nodeOps.parentNode(oldElm)

        //  Create and insert nodes 
        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)
        )

        // //  recursive   Update parent placeholder 
        // 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 nodes 
        // destroy old node
        if (isDef(parentElm)) {
    
          removeVnodes([oldVnode], 0, 0)
        } else if (isDef(oldVnode.tag)) {
    
          invokeDestroyHook(oldVnode)
        }
    }
}

to update + Than the child nodes patchVnode

This function is called recursively updateChildren Entrance , Except for the pair of child nodes , It will also update the things on the old node to the new node , stay updateChildren Function , This method is called by the update node

This method is better than the logic of child nodes :

The new node is a text node : go setTextContent, Update text content
The new node has children

Both the new node and the old node have child nodes : If the subsets are completely consistent, do not update , Inconsistent walk updateChildren Compare
Only new nodes have children : There is no need to compare , Insert the new node directly into elm( Father dom) Next addVnodes
Only the old node has child nodes : There is no need to compare , Delete all child nodes removeVnodes
The old node is a text node : go setTextContent Empty text

function patchVnode(
    oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly
  ) {
    
    if (oldVnode === vnode) {
    
      return
    }
    if (isDef(vnode.elm) && isDef(ownerArray)) {
    
      // clone reused vnode
      vnode = ownerArray[index] = cloneVNode(vnode)
    }

    //  Let the new vnode Binding old vnode The reality bound dom(elm)
    const elm = vnode.elm = oldVnode.elm

    //  Asynchronous processing 
    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.
    //  Be careful , We only in vnode Only when cloned —— If the new node is not cloned , This means that the rendering function has been hot reloaded api Reset , We need to re render appropriately .
    //  Static node or once Words , Don't execute 
    if (isTrue(vnode.isStatic) &&
      isTrue(oldVnode.isStatic) &&
      vnode.key === oldVnode.key &&
      (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
    ) {
    
      vnode.componentInstance = oldVnode.componentInstance
      return
    }

    /** *  This is called prepatch function  *  Only components have this function  * prepatch Function creation location :create-component.js * prepatch Function called internally updateChildComponent Method (lifecycle.js) *  Finally updated the component instance (oldVnode.componentInstance) A series of properties in , And put it in vnode.componentInstance On  * ( Called internally $forceUpdate()) */
    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

    //  call update  Hook function update 
    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 the new node is not a text or comment node 
    if (isUndef(vnode.text)) {
    
      if (isDef(oldCh) && isDef(ch)) {
     //  Both old and new nodes have child nodes 
        //  The child nodes of the new and old nodes are not always , call updateChildren(diff Algorithm )
        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
      } else if (isDef(ch)) {
     //  Only new nodes have children 
        //  If the old node is a text node 、 Empty text 
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
        // addVnodes: take ch Batch insert into elm Next 
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
      } else if (isDef(oldCh)) {
     //  Only the old node has child nodes 
        //  Delete all child nodes 
        removeVnodes(oldCh, 0, oldCh.length - 1)
      } else if (isDef(oldVnode.text)) {
     //  New and old nodes have no child nodes , But when the old node is a text node 
        nodeOps.setTextContent(elm, '') // => elm.text = ''
      }
    } else if (oldVnode.text !== vnode.text) {
     //  The new node is a text node  && ( New and old node texts are different || The old node is not a text node )
      nodeOps.setTextContent(elm, vnode.text) // => elm.text = vnode.text
    }
    //  If there is postpatch, perform postpatch Hook function , Custom hook function 
    if (isDef(data)) {
    
      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
    }
}

diff Algorithm

The core approach updateChildren

Calling function updateChildren It's a kind of reference , Two of the parameters are new and old elements children

oldCh
newCh

Create variables

Then get the beginning of the two arrays 、 End element and index

//  Old element  children list  relevant 
let oldStartIdx = 0 //  Indexes - head 
let oldEndIdx = oldCh.length - 1 //  Indexes - tail 
let oldStartVnode = oldCh[0] //  Elements - head 
let oldEndVnode = oldCh[oldEndIdx] //  Elements - tail 

//  The new element  children list  relevant 
let newStartIdx = 0 //  Indexes - head 
let newEndIdx = newCh.length - 1 //  Indexes - tail 
let newStartVnode = newCh[0] //  Elements - head 
let newEndVnode = newCh[newEndIdx] //  Elements - tail 

Into the loop

And then through while sentence , Cycle calculation :

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    ...}
// while ( The index at the beginning of the old element  <=  Old element end index  &&  The index at the beginning of the new element  <=  New element end index ) {...}

In circulation , Double pointers are used to deal with new nodes and old nodes at the same time , One party's cycle is complete , Just close the loop

Empty data skip

First , When encountering empty data, skip , It is used for compatibility with the out of order comparison mentioned later

while (...) {
    
    if (isUndef(oldStartVnode)) {
    
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
    } else if (isUndef(oldEndVnode)) {
    
        oldEndVnode = oldCh[--oldEndIdx]
    } else if (...){
    
        ...
    }
}

Sequence comparison

then , Through the above sameVnode Function to compare whether the new and old nodes are the same , If the same

  1. perform patchVnode function ( The function mentioned above ) Update new nodes while performing recursive processing
  2. Update the of the two nodes compared vnode And index

In the code below ,vue Used 4 There are two different ways to compare , Namely

The head of the new and old nodes is compared with the head
The tail of new and old nodes is compared with the tail
The head of the old node is compared with the tail of the new node
The tail of the old node is compared with the head of the new node

Look at the code first , The following will explain four ways of comparison

//  Follow the code block above 
while (...) {
    
    ...
    else if (sameVnode(oldStartVnode, newStartVnode)) {
     //  The new and old nodes are the same  ---  Head to head comparison : old man <-> New head 
        //  go patchVnode( recursive ), Replace old and new nodes 
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
        } else if (sameVnode(oldEndVnode, newEndVnode)) {
     //  The new and old nodes are the same  ---  Tail to tail comparison : Old tail <-> New tail 
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
    } else if (sameVnode(oldStartVnode, newEndVnode)) {
     //  The new and old nodes are the same  ---  Head to tail comparison : old man <-> New tail 
        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)) {
     //  The new and old nodes are the same  ---  The tail is more : Old tail <-> New head 
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
    } else {
    
        ...
    }
}

The head of the new and old nodes is compared with the head

Before change example 1
newStartIdx:0newEndIdx:3
ABCD
ABCD
oldStartIdx:0oldEndIdx:3

Enter the cycle for the first time , The head of the new and old nodes is compared with the head It's based on newStartIdx and oldStartIdx Contrast , If they are the same , call patchVnode After the newStartIdx and oldStartIdx Set to 1, And update the oldStartVnode,newStartVnode

After change example 1
oldStartIdx:1oldEndIdx:3
ABCD
ABCD
newStartIdx:1newEndIdx:3

The tail of new and old nodes is compared with the tail

Before change example 2
oldStartIdx:0oldEndIdx:3
ABCD
ABCD
newStartIdx:0newEndIdx:3

The same idea as the head comparison , take newEndIdx and oldEndIdx Contrast , If they are the same , call patchVnode After the newEndIdx and oldEndIdx Set to 2, And update the oldEndVnode,newEndVnode

After change example 2
oldStartIdx:0oldEndIdx:2
ABCD
ABCD
newStartIdx:0newEndIdx:2

The head of the old node is compared with the tail of the new node

Before change example 3
oldStartIdx:0oldEndIdx:3
DABC
ABCD
newStartIdx:0newEndIdx:3

In this comparison , Will oldStartIdx and newEndIdx Contrast , If they are the same , call patchVnode, And then call DOM A native method insertBefore take oldStartVnode The corresponding real node is placed in oldEndVnode Behind the corresponding real node

canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
// ↓↓↓↓↓ What the above sentence actually does ↓↓↓↓↓↓
// parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling)

At present, the real node + Corresponding to virtual DOM in xxxIdx The appearance of :

real dom example 3
oldStartIdx:0oldEndIdx:3
ABCD
newStartIdx:0newEndIdx:3

In the example ,insertBefore Just be true DOM"D" To shift backward , And the virtual in the loop dom Indexes in are not affected

The final will be oldStartIdx Set to 1, take newEndIdx Set to 2, And update the oldStartVnode,newEndVnode

After change example 3
oldStartIdx:1oldEndIdx:3
DABC
ABCD
newStartIdx:0newEndIdx:2

The tail of the old node is compared with the head of the new node

Before change example 4
oldStartIdx:0oldEndIdx:3
ABCD
DABC
newStartIdx:0newEndIdx:3

The comparison idea is the same as that of the head of the old node and the tail of the new node , Will newStartIdx and oldEndIdx Contrast , If they are the same , call patchVnode, And then call DOM A native method insertBefore take newStartVnode The corresponding real node is placed in newEndVnode Behind the corresponding real node

canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, nodeOps.nextSibling(oldStartVnode.elm))
// ↓↓↓↓↓ What the above sentence actually does ↓↓↓↓↓↓
// parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm.nextSibling)

At present, the real node + Corresponding to virtual DOM in xxxIdx The appearance of :

real dom example 4
oldStartIdx:0oldEndIdx:3
DABC
newStartIdx:0newEndIdx:3

In the example ,insertBefore Just be true DOM"D" Forward shift , And the virtual in the loop dom Indexes in are not affected

The final will be oldEndIdx Set to 2, take newStartIdx Set to 1, And update the oldEndVnode,newStartVnode

After change example 4
oldStartIdx:0oldEndIdx:2
ABCD
DABC
newStartIdx:1newEndIdx:3

Out of order comparison

When the above order comparison does not enter if Under the circumstances , Will go else Out of order contrast logic in , The most chaotic part will be analyzed line by line :

The idea in disorderly comparison is : With newCh The next node to be processed in the positive order (newStartVnode) Based on , Go to oldCh Find out whether there is data that can be reused , Some words are the same as the above order , go patchVnode to update , If not, go createElm Create a new node

  1. determine oldCh Are there any reusable old nodes in the
    Walk into... For the first time else When judging , take oldCh Array Untreated The data is transformed into {[oldCh.key]:[oldCh Of index]} In the form of , And deposit in oldKeyToIdx Properties of the , On the second entry else Use it directly when judging
    And then through oldKeyToIdx[newStartVnode.key] You can judge oldCh Is there any and In the current loop newStartVnode.key identical key The old node of , And can be based on oldCh[oldKeyToIdx[newStartVnode.key]] Find the node
    If no reusable node is found , Will pass findIdxInOld The method is again in oldCh Find it in
//  take oldCh The unprocessed data in the array is converted and stored in oldKeyToIdx Properties of the 
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
//  If there is key, Can pass key Get oldKeyToIdx The corresponding... In the list index, without key, Then traverse to find index
idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)


//  The following is the method used in the above code :

//  Return to one {key: index} The object of 
function createKeyToOldIdx(children, beginIdx, endIdx) {
    
    let i, key
    const map = {
    }
    for (i = beginIdx; i <= endIdx; ++i) {
    
        key = children[i].key
        if (isDef(key)) map[key] = i
    }
    return map
}
//  lookup node stay oldCh Medium index
function findIdxInOld (node, oldCh, start, end) {
    
    for (let i = start; i < end; i++) {
    
        const c = oldCh[i]
        // sameVnode  The method mentioned above , The effect is through tag Wait for the content to judge two vnode Are they the same? 
        if (isDef(c) && sameVnode(node, c)) return i
    }
}
  1. Judge the... Obtained in the first step idxInOld If there is a value , If there is no value , On behalf of vnode(newStartVnode) It's a new node , go createElm establish
if (isUndef(idxInOld)) {
    
    createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
    ...
  1. If idxInOld Valuable
    adopt idxInOld Get the reusable node in the old node vnodeToMovevnodeToMove = oldCh[idxInOld]
    Judge the current processing node newStartVnode and vnodeToMove Are they the same?
    If it's the same
    go patchVnode Update node , take oldCh The corresponding value in the old node list (oldCh[idxInOld]) Set to undefined, Used to identify that the node has been processed .( Because this node does not pass through oldStartIdx perhaps oldEndIdx To get the , So there's no way to update these two when comparing the order oldxxxIdx Value to identify that the node has been processed , Set it here to undefined after , When while Cycle through oldxxxIdx When the node is obtained , You can use the above Empty data skip The code at skipped the node operation )
    The final will be vnodeToMove The real DOM adopt insertBefore Method is inserted into the next node to be judged (oldStartVnode) Just in front of
    If it's not the same
    Here, if you judge newStartVnode and vnodeToMove Different , For two vnode Only key identical , Cannot reuse , go createElm establish
} else {
    
    //  obtain oldCh The reusable node in the 
    vnodeToMove = oldCh[idxInOld]
    //  Judge whether the currently processed nodes are the same  
    if (sameVnode(vnodeToMove, newStartVnode)) {
    
        //  The same idea as the sequence comparison , go patchVnode to update 
        patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        //  Set the old node to undefined
        oldCh[idxInOld] = undefined
        //  Make the real thing you want to move DOM Insert into the next node that needs to be judged 
        canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
    } else {
    
        //  Cannot reuse , go `createElm` establish 
        createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
    }
}
  1. Last ++newStartIdx, And update the newStartVnode
//  to update newStartVnode and ++newStartIdx
newStartVnode = newCh[++newStartIdx]

Out of order comparison of the complete code

while (...) {
    
    if (isUndef(oldStartVnode)) {
    
        ...
    } else if (...) {
    
        ...
    } else {
    
        //  take oldCh The unprocessed data in the array is converted and stored in oldKeyToIdx Properties of the 
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        //  If there is key, Can pass key Get oldKeyToIdx The corresponding... In the list index, without key, Then traverse to find index
        idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
        if (isUndef(idxInOld)) {
    
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
        } else {
    
            //  obtain oldCh The reusable node in the 
            vnodeToMove = oldCh[idxInOld]
            //  Judge whether the currently processed nodes are the same  
            if (sameVnode(vnodeToMove, newStartVnode)) {
    
                //  The same idea as the sequence comparison , go patchVnode to update 
                patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
                //  Set the old node to undefined
                oldCh[idxInOld] = undefined
                //  Make the real thing you want to move DOM Insert into the next node that needs to be judged 
                canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
            } else {
    
                //  Cannot reuse , go createElm establish 
                createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
            }
        }
        //  to update newStartVnode and ++newStartIdx
        newStartVnode = newCh[++newStartIdx]
    }
}

After jumping out of the loop

Because the judgment of the cycle is oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx, When you jump out of the loop , There may be a new node list Or old node list Are there any processed nodes , Here we need to deal with these nodes
** There is unprocessed data in the new node list :** There is no content in the old node that can be judged , It means that the remaining new nodes need to be recreated DOM The node of , So direct loop call createElm That's all right.
** There is unprocessed data in the old node list :** After the new node is processed , If there is unprocessed data in the old node list , It means that the remaining data is to be deleted , call removeVnodes Delete the remaining nodes

if (oldStartIdx > oldEndIdx) {
     //  The old node list has been processed 
    refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm //  Get the first element after the position of the node to be inserted in the real node ( be used for Dom.insertBefore It's a kind of reference )
    //  Create all newCh Unprocessed nodes in ( Cycle call createElm)
    addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
     //  The new node list has been processed 
    removeVnodes(oldCh, oldStartIdx, oldEndIdx) //  Delete all oldCh Unprocessed nodes in 
}

------------end------------

copyright notice
author[Searching],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2022/119/202204290349128601.html

Random recommended