preface

Recently, I reviewed the virtual DOM and Diff and read many materials. I hereby summarize this long article to deepen my understanding of VUE. Another reason is that by the end of the year, to see their half-dead digging force value, level3 from so close and so far away from me, I just want to cry, for the KPI, I dashed, vomiting blood finishing this article, the title is more modest, although I’m reminded of rushed performance up to that kind of easing the bit in the interviewer, such as the title, but think about or forget it, Once the dragon slayer boy really want to become a dragon? Don’t be the person you hate for profit. This article more detailed analysis of vue virtual DOM,Diff algorithm, some of the key places to carry some graphs from elsewhere to illustrate (thanks to cartography big guy), also contains more detailed source code interpretation. If you feel the article is good, please give me a thumbs-up. If you have any mistakes, please point them out in the comment section. Of course, if you don’t understand the part, please also ask questions.

Rendering of the real DOM

Before we get to the virtual DOM, let’s talk about rendering the real DOM.

The actual browser DOM rendering process can be divided into the following parts

  1. Build the DOM tree. HTML tags are parsed through the HTML Parser and built into a DOM tree. When the parser encounters non-blocking resources (images, CSS), it continues parsing, but if it encounters script tags (especially without the async and defer attributes), Blocks rendering and stops parsing the HTML, which is why it is best to place the script tag under the body.
  2. Build the CSSOM tree. Just like building the DOM, browsers build style rules into CSSOM. The browser iterates through the CSS rule set, creating a tree of nodes with parent-child, sibling, and other relationships based on the CSS selector.
  3. Build the Render tree. This step associates the DOM and CSSOM to determine what CSS rules should apply to each DOM element. All relevant styles are matched to each visible node in the DOM tree, and the computed style of each node is determined according to the CSS cascade. Invisible nodes (head, nodes whose attributes include display: None) are not generated into the Render tree.
  4. Layout/Reflow. The first time the browser determines the location and size of a node is called layout, and if the location and size of a node subsequently change, this step triggers layout adjustments, known as backflow.
  5. Paint/Repaint. Draws each visual portion of an element onto the screen, including text, colors, borders, shadows, and replacement elements such as buttons and images. Repaint is triggered when elements such as text, colors, borders, and shadows change. To ensure that redrawing can be done faster than the original drawing, the drawing on the screen is usually broken up into layers. Moving content up to the GPU layer (which can be triggered using Tranform,filter,will-change, or opacity) improves drawing and redrawing performance.
  6. Synthesis (Compositing). This step merges the layers in the drawing process, ensuring that they are drawn in the right order to display the right content on the screen.

Why do we need the virtual DOM

This is a DOM rendering process. If the DOM is updated, the DOM needs to be re-rendered if the following situation exists

<body>
    <div id="container">
        <div class="content" style="color: red; font-size:16px;">
            This is a container
        </div>.<div class="content" style="color: red; font-size:16px;">
            This is a container
        </div>
    </div>
</body>
<script>
    let content = document.getElementsByClassName('content');
    for (let i = 0; i < 1000000; i++) {
        content[i].innerHTML = `This is a content${i}`;
        // Trigger backflow
        content[i].style.fontSize = `20px`;
    }
</script>
Copy the code

Then the actual operation DOM100w times is required, triggering backflow 100W times. Each DOM update is followed by an undifferentiated update of the real DOM. So there’s a lot of wasted performance. If the loop is a complex operation that frequently triggers backflow and redraw, then performance can easily suffer and stall. In addition, it is important to note that the virtual DOM is not meant to be faster than THE DOM. The performance of the virtual DOM depends on the scene. The performance of the virtual DOM is positively related to the size of the template. The comparison process of virtual DOM does not distinguish the size of data. When there are only a small number of dynamic nodes inside the component, virtual DOM will still traverse the entire VDOM, which is more than a layer of operation compared to direct rendering.

	<div class="list">
    <p class="item">item</p>
    <p class="item">item</p>
    <p class="item">item</p>
    <p class="item">{{ item }}</p>
    <p class="item">item</p>
    <p class="item">item</p>
  </div>
Copy the code

Take the example above, the virtual DOM. Although there is only one dynamic node, the virtual DOM still needs to traverse the entire list of classes, text, tags and other information, and finally still needs to perform DOM rendering. If it’s just DOM manipulation, just manipulate a specific DOM and render it. The core value of virtual DOM lies in that it can describe the real DOM through JS with stronger expression power. Through declarative language operation, it provides more convenient and fast development experience for developers. Moreover, in most scenarios without manual optimization, it ensures lower performance and higher cost performance.

Virtual DOM

The virtual DOM is essentially a JS object that represents the real DOM structure. Tags are used to describe tags, props are used to describe attributes, and children are used to represent nested hierarchies.

const vnode = {
    tag: 'div'.props: {
        id: 'container',},children: [{
        tag: 'div'.props: {
            class: 'content',},text: 'This is a container'}}]// The corresponding real DOM structure
<div id="container">
  <div class="content">
    This is a container
  </div>
</div>
Copy the code

The update of the virtual DOM will not operate DOM immediately, but will find out the nodes to be updated through the DIff algorithm, update as needed, and save the updated content as a JS object, and then mount it to the real DOM to realize the real DOM update. Through the virtual DOM, three problems of manipulating the real DOM are solved.

  1. Frequent updates cause frequent DOM updates, causing performance problems
  2. Frequent reflow and redraw
  3. Development experience

In addition, because the virtual DOM stores JS objects, it naturally has cross-platform capabilities, not just limited to browsers.

advantages

To sum up, the virtual DOM has several advantages

  1. There is no need to update DOM frequently for small changes. The diff algorithm of the framework will automatically compare and analyze nodes that need to be updated, and update them as needed
  2. Updating data does not result in frequent backflow and redraw
  3. Stronger expression, more convenient data update
  4. Save js objects, with cross-platform capabilities

insufficient

The virtual DOM also has its drawbacks. When rendering a large number of DOM for the first time, it is slower than innerHTML insertion due to the extra layer of virtual DOM computation.

Virtual DOM implementation principle

It is divided into three parts

  1. Create node description object through JS
  2. Diff algorithm compares and analyzes the differences between the old and new virtual DOM
  3. Update the difference patch to the real DOM

The Diff algorithm

In order to avoid unnecessary rendering and update as needed, virtual DOM will use Diff algorithm to compare virtual DOM nodes and compare node differences, so as to determine the nodes to be updated before rendering. Vue adopts a depth-first, same-level comparison strategy.

The comparison of the new node to the old node revolves around three things for rendering purposes

  1. Creating a New node
  2. Deleting an Obsolete Node
  3. Updating an existing node

How do you compare old and new nodes?

function sameVnode(a, b) {
    return (
        a.key === b.key &&
        a.asyncFactory === b.asyncFactory && (
            (
                a.tag === b.tag &&
                a.isComment === b.isComment &&
                isDef(a.data) === isDef(b.data) &&
                sameInputType(a, b) // Processing of the input node
            ) || (
                isTrue(a.isAsyncPlaceholder) &&
                isUndef(b.asyncFactory.error)
            )
        )
    )
}

// Check whether the two nodes are of the same input type
function sameInputType(a, b) {
    if(a.tag ! = ='input') return true
    let i
    const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type
    const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type
    // Input type is the same or both types are text
    return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
}
Copy the code

In vue, data is used to represent the props of vnode, and isComment is used. In addition, special processing will be performed when the input is used.

Creating a New node

When the new node has one and the old node doesn’t, it means the new content node. Only element nodes, text nodes, and comment nodes can be created and inserted into the DOM.

Delete old nodes

When the old node has one and the new node does not, it means that the new node has given up part of the old node. Deleting a node removes the children of the old node.

Update the node

Both the new node and the old node exist, so everything is subject to the new, update the old node. How do you determine whether a node needs to be updated?

  • Determine whether the new node is exactly the same as the old node, so there is no need to update
  // Check whether vNode and oldVnode are identical
  if (oldVnode === vnode) {
    return;
  }
Copy the code
  • Check whether the new node and the old node are static, whether the key is the same, whether the new node is a clone (if not, it means that the render function has been reset, in which case it needs to be re-rendered), or whether the once attribute is set, and replace componentInstance if the condition is met
  // Whether the node is static, whether the key is the same, whether the node is cloned, or whether the once attribute is set
  if (
    isTrue(vnode.isStatic) &&
    isTrue(oldVnode.isStatic) &&
    vnode.key === oldVnode.key &&
    (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
  ) {
    vnode.componentInstance = oldVnode.componentInstance;
    return;
  }
Copy the code
  • Judge whether the new node has text (judged by the text attribute). If there is text, compare the old node at the same level. If the text of the old node is different from the text of the new node, then adopt the new text content. If the new node has no text, then you need to make a judgment about the child node later
// Determine if the new node has text
if (isUndef(vnode.text)) {
  // If there is no text, process the code associated with the child node. }else if(oldVnode.text ! == vnode.text) {// The new node text replaces the old node text
  nodeOps.setTextContent(elm, vnode.text)
}
Copy the code
  • Determine the status of the new node and the children of the old node. There are four different scenarios
    1. Both the new node and the old node have children
    2. Only new nodes have children
    3. Only old nodes have children
    4. Neither the new node nor the old node has children

They all have child nodes

If both nodes have children, the old and new nodes need to be compared. If they are different, a diff operation needs to be performed. In VUE, this is the updateChildren method, more on this later.

// Determine if the new node has text
if (isUndef(vnode.text)) {
    // The updateChildren method is used to compare children of both the old and new nodes if they are different
    if (isDef(oldCh) && isDef(ch)) {
        if(oldCh ! == ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) } }else if(oldVnode.text ! == vnode.text) {// The new node text replaces the old node text
    nodeOps.setTextContent(elm, vnode.text)
}
Copy the code

Only new nodes have children

Only new node has child nodes, it represents the new content, it is a new child node to the DOM, before new will do a repeat key inspection, and make a reminder, should also consider that if the old node is just a text node, no children, this case will need to empty the old node text content.

// Only new nodes have children
if (isDef(ch)) {
  // Check duplicate keys
  if(process.env.NODE_ENV ! = ='production') {
    checkDuplicateKeys(ch)
  }
  // Clear the old node text
  if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, ' ')
  // Add a new node
  addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
}

// Check duplicate keys
function checkDuplicateKeys(children) {
  const seenKeys = {}
  for (let i = 0; i < children.length; i++) {
      const vnode = children[i]
      // Each Key of the child node
      const key = vnode.key
      if (isDef(key)) {
          if (seenKeys[key]) {
              warn(
                  `Duplicate keys detected: '${key}'. This may cause an update error.`,
                  vnode.context
              )
          } else {
              seenKeys[key] = true}}}}Copy the code

Only old nodes have children

Only the old node has it, which means that the new node abandons the child node of the old node, so the child node of the old node needs to be deleted

if (isDef(oldCh)) {
  // Delete the old node
  removeVnodes(oldCh, 0, oldCh.length - 1)}Copy the code

They have no child nodes

At this time, we need to judge the text of the old node to see whether there is text in the old node. If so, we will clear it

if (isDef(oldVnode.text)) {
  / / to empty
  nodeOps.setTextContent(elm, ' ')}Copy the code

The overall logical code is as follows

function patchVnode(oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
    // Check whether vNode and oldVnode are identical
    if (oldVnode === vnode) {
        return
    }

    if (isDef(vnode.elm) && isDef(ownerArray)) {
        // Clone reuses nodes
        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
    }
		// Whether the node is static, whether the key is the same, whether the node is cloned, or whether the once attribute is set
    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)) {
      	// Call the update callback and update hook
        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)
    }
    // Determine if the new node has text
    if (isUndef(vnode.text)) {
      	// The updateChildren method is used to compare children of both the old and new nodes if they are different
        if (isDef(oldCh) && isDef(ch)) {
            if(oldCh ! == ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) }else if (isDef(ch)) {
          	// Only new nodes have children
            if(process.env.NODE_ENV ! = ='production') {
              	// Repeat the Key check
                checkDuplicateKeys(ch)
            }
          	// Clear the old node text
            if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, ' ')
          	// Add a new node
            addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
        } else if (isDef(oldCh)) {
          	// Only the old node has children. Delete the old node
            removeVnodes(oldCh, 0, oldCh.length - 1)}else if (isDef(oldVnode.text)) {
          	// The old and new nodes have no children
            nodeOps.setTextContent(elm, ' ')}}else if(oldVnode.text ! == vnode.text) {// The new node text replaces the old node text
        nodeOps.setTextContent(elm, vnode.text)
    }

    if (isDef(data)) {
        if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
    }
}
Copy the code

It will be clearer with a flow chart

Comparison of child nodes is updated with updateChildren

When both old and new nodes have children, the updateChildren method is called to compare and update the children. So, in terms of data, the old and new nodes are stored in two arrays.

const oldCh = [oldVnode1, oldVnode2,oldVnode3];
const newCh = [newVnode1, newVnode2,newVnode3];
Copy the code

The child node is updated using a two-ended comparison strategy. What is a two-ended comparison strategy? The old node and the new node are compared by comparing the first and last elements (there are four comparisons), and then comparing them in the middle (newStartIdx, increasing with oldStartIdx,newEndIdx, decreasing with oldEndIdx).

Compare the process

Move to the center

Here is a description of the new front, new rear, old front, old rear

  1. New before refers to the first element in the unprocessed child node array of the new node, corresponding to newStartVnode in the vUE source code
  2. New refers to the last element in the unprocessed child node array of the new node, corresponding to newEndVnode in the vUE source code
  3. Old, refers to the first element in the unprocessed child node array of the old node, corresponding to oldStartVnode in the vUE source code
  4. Old refers to the last element in the unprocessed child node array of the old node, corresponding to oldEndVnode in the vUE source code

The child node compares the process

The next step is to explain the above comparison process and the operations done after the comparison

  • Compare the new node with the old one. If they are the same, then update the patchVnode as mentioned above, and then compare the second one with the new one. They don’t compare things in a different way until they’re different

if (sameVnode(oldStartVnode, newStartVnode)) {
  // Update the child node
  patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
  // New and old step back
  oldStartVnode = oldCh[++oldStartIdx]
  newStartVnode = newCh[++newStartIdx]
}
Copy the code
  • Compare the new with the old, if they are the same, do the same pathchVnode update, and then the old and the new step forward, do the previous comparison… They don’t change the way they compare until they’re different

if (sameVnode(oldEndVnode, newEndVnode)) {
    // Update the child node
    patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
    // Old and new forward
    oldEndVnode = oldCh[--oldEndIdx]
    newEndVnode = newCh[--newEndIdx]
}
Copy the code
  • If they are the same, an update operation is performed, and then the old front is moved to the end of the array of all unprocessed old nodes, so that the old front and the new back position are the same, and then both sides move together to the middle, the new forward, the old backward. If not, the comparison mode will continue to be switched

if (sameVnode(oldStartVnode, newEndVnode)) {
  patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
  // Move the first child of the old child array to the end
  canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
  / / the old backward
  oldStartVnode = oldCh[++oldStartIdx]
  / / new forward
  newEndVnode = newCh[--newEndIdx]
Copy the code
  • If they are the same, update them. Then move the old node to the top of the array of all unprocessed old nodes, keeping the old node in the same position as the new node, and then move the new node back, the old node forward, and continue to move toward the middle. Continue to compare the remaining nodes. If not, the traditional looping lookup is used.

if (sameVnode(oldEndVnode, newStartVnode)) {
  patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
  // Insert the old back move to the front
  canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
  / / the old forward
  oldEndVnode = oldCh[--oldEndIdx]
  / / new backward
  newStartVnode = newCh[++newStartIdx]
}
Copy the code
  • Iterating through the search, if none of the above is found, it looks for a match through the key.

{key:index} createKeyToOldIdx {key:index}

CreateKeyToOldIdx {key:index} {key:index}
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
Copy the code

If the new node also has no key, the findIdxInOld method is executed to match the old node from start to finish

// Use the key of the new node to find the subscript of the new node in the old node. If the key is not set, the traversal operation is performed to find the new node
idxInOld = isDef(newStartVnode.key)
  ? oldKeyToIdx[newStartVnode.key]
  : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)

/ / findIdxInOld method
function findIdxInOld(node, oldCh, start, end) {
  for (let i = start; i < end; i++) {
    const c = oldCh[i]
    // find the same node subscript
    if (isDef(c) && sameVnode(node, c)) return i
  }
}
Copy the code

If the new node still does not match the old node’s subscript, it indicates that the node is a new node, then perform the new operation.

// If the new node cannot find its subscript in the old node, it is a new element and performs the new operation
if (isUndef(idxInOld)) {
  createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
Copy the code

If yes, an old node with the same key or the same node and key has been found. If the nodes are the same, the old nodes need to be moved before all unprocessed nodes after patchVnode. For nodes with the same key and different elements, they are considered as new nodes and new operations are performed. After the execution is complete, the new node takes a step back.

// If the new node cannot find its subscript in the old node, it is a new element and performs the new operation
if (isUndef(idxInOld)) {
  // Add a new element
  createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
  // A node with the same key was found in the old node
  vnodeToMove = oldCh[idxInOld]
  if (sameVnode(vnodeToMove, newStartVnode)) {
    // Same child node update operation
    patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
    // Assign undefined to the old node after the update
    oldCh[idxInOld] = undefined
    // Move the old node to the front of all unprocessed nodes
    canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
  } else {
    // If it is the same key, different elements, as a new node, perform the create operation
    createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
  }
}
// The new node goes backwards
newStartVnode = newCh[++newStartIdx]
Copy the code

If the traversal of the old node is completed but the new node is not completed, it indicates that the subsequent nodes are newly added and the new operation is performed. If the traversal of the new node is completed but the traversal of the old node is not completed, it indicates that the old node has redundant nodes and the deletion operation is performed.

// The old node is traversed, but the new node is not traversed,
if (oldStartIdx > oldEndIdx) {
  refElm = isUndef(newCh[newEndIdx + 1])?null : newCh[newEndIdx + 1].elm
  // Add a node
  addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
  // Delete unnecessary old nodes
  removeVnodes(oldCh, oldStartIdx, oldEndIdx)
}
Copy the code

Summary of child node comparison

Above is a complete process of child node comparison update, this is the complete logical code

function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0
    let newStartIdx = 0
    let oldEndIdx = oldCh.length - 1
    let oldStartVnode = oldCh[0] / / the old before
    let oldEndVnode = oldCh[oldEndIdx] / / after the old
    let newEndIdx = newCh.length - 1
    let newStartVnode = newCh[0] / / new front
    let newEndVnode = newCh[newEndIdx] / / new after
    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
    constcanMove = ! removeOnlyif(process.env.NODE_ENV ! = ='production') {
        checkDuplicateKeys(newCh)
    }

    // Double end comparison traversal
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
        if (isUndef(oldStartVnode)) {
            // Move the old forward and backward
            oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
        } else if (isUndef(oldEndVnode)) {
            // The old rear moves forward
            oldEndVnode = oldCh[--oldEndIdx]
        } else if (sameVnode(oldStartVnode, newStartVnode)) {
            // The new and the old
            // Update the child node
            patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
                // New and old step back
            oldStartVnode = oldCh[++oldStartIdx]
            newStartVnode = newCh[++newStartIdx]
        } else if (sameVnode(oldEndVnode, newEndVnode)) {
            // New queen and old queen
            // Update the child node
            patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
                // Old and new take a step forward
            oldEndVnode = oldCh[--oldEndIdx]
            newEndVnode = newCh[--newEndIdx]
        } else if (sameVnode(oldStartVnode, newEndVnode)) {
            // The new and the old
            // Update the child node
            patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
                // Insert the old front move to the end
            canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
                // New forward, old backward
            oldStartVnode = oldCh[++oldStartIdx]
            newEndVnode = newCh[--newEndIdx]
        } else if (sameVnode(oldEndVnode, newStartVnode)) {
            // New before and old after
            patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)

            // Insert the old back move to the front
            canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)

            // New backward, old forward
            oldEndVnode = oldCh[--oldEndIdx]
            newStartVnode = newCh[++newStartIdx]
        } else {
            CreateKeyToOldIdx {key:index} {key:index}
            if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)

            // Use the key of the new node to find the subscript of the new node in the old node. If the key is not set, the traversal operation is performed to find the new node
            idxInOld = isDef(newStartVnode.key) ?
                oldKeyToIdx[newStartVnode.key] :
                findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)

            // If the new node cannot find its subscript in the old node, it is a new element and performs the new operation
            if (isUndef(idxInOld)) {
                // Add a new element
                createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
            } else {
                // A node with the same key was found in the old node
                vnodeToMove = oldCh[idxInOld]
                if (sameVnode(vnodeToMove, newStartVnode)) {
                    // Same child node update operation
                    patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
                        // Assign undefined to the old node after the update
                    oldCh[idxInOld] = undefined
                        // Move the old node to the front of all unprocessed nodes
                    canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
                } else {
                    // If it is the same key, different elements, as a new node, perform the create operation
                    createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
                }
            }
            // The new node takes a step back
            newStartVnode = newCh[++newStartIdx]
        }
    }

    // The old node is traversed, but the new node is not traversed,
    if (oldStartIdx > oldEndIdx) {
        refElm = isUndef(newCh[newEndIdx + 1])?null : newCh[newEndIdx + 1].elm
            // Add a node
        addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else if (newStartIdx > newEndIdx) {
        // Delete unnecessary old nodes
        removeVnodes(oldCh, oldStartIdx, oldEndIdx)
    }
}
Copy the code

The last

Recently, we plan to set up a small group, especially welcome active love sharing love “pretend to force” students to join, usually have problems to help each other, no problem to touch fish chat, together to share to improve work efficiency, to achieve common touch fish methods. If you want to join us, please contact me.

The resources

  1. VirtualDOM with diff
  2. Render pages: How browsers work
  3. The DOM – Diff Vue
  4. In-depth analysis: Vue core virtual DOM