Diff

The biggest purpose of VNode is to generate virtual DOM nodes corresponding to the real DOM before and after data changes. Then, you can compare the old and new VNodes to find out the differences, and then update the DOM nodes with the differences, and finally achieve the purpose of updating the view with the least operation on the real DOM

The Diff algorithm is at the heart of the virtual DOM

The so-called oldVNode (namely oldVNode) is the virtual DOM node corresponding to the view before the data changes, while the new VNode is the virtual DOM node corresponding to the new view to be rendered after the data changes. Therefore, we need to take the generated new VNode as the benchmark to compare the old oldVNode. If the new VNode has a node but the old oldVNode does not, add it to the old oldVNode. If the new VNode does not have a node but the old oldVNode does, remove it from the old oldVNode. If some nodes exist on both the new VNode and the old oldVNode, the old oldVNode is updated using the new VNode to make the old and new vNodes the same

Summary: Based on the new VNode, the patch process is to modify the old oldVNode to look like the new VNode.

  • Create a node: newVNodeThere are old onesoldVNodeNo, it’s in the old oneoldVNodeCreated in.
  • Delete a node: newVNodeThere is no oldoldVNodeThere is, from the oldoldVNodeRemoved.
  • Update node: newVNodeAnd the oldoldVNodeIf there is, take a new oneVNodePrevail, update the oldoldVNode

Static node

<p>I am the words that will not change</p>
Copy the code

This node contains only plain text and no mutable variables. This means that no matter how the data changes, as long as the node is rendered for the first time, it will never change. This is because it contains no variables, so it has nothing to do with any changes in the data. We call this kind of node a static node.

patch

If both VNode and oldVNode are static nodes

As we said, static nodes have nothing to do with any changes to their data, so if they are all static nodes, they will be skipped without processing.

If VNode is a text node

If a VNode is a text node, that means it contains only plain text, then simply check whether the oldVNode is also a text node. If so, compare the two texts. If not, change the text in the oldVNode to be the same as the text in the VNode. If oldVNode is not a text node, then call setTextNode to change it to a text node, whatever it is, with the same text content as VNode.

If VNode is an element node

If a VNode is an element node, the following two cases are subdivided:

  • This node contains child nodes

    If the new node contains child nodes, it is necessary to see whether the old node contains child nodes. If the old node also contains child nodes, it is necessary to update the child nodes by recursive comparison. If the old node has no children, so the old node is likely to be empty node or a text node, if the old node is empty the child nodes of the new node in the create a node and then inserted into the old, if the old node is a text node, then the text to empty, then the child nodes in the new node to create a node and then inserted into the old.

  • This node does not contain child nodes

    If the node contains no children and it is not a text node, then the node is empty, and it is easy to empty whatever was inside the old node.

  /* Update node */
  function patchVnode (oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
    /* If it is exactly the same, return */
    if (oldVnode === vnode) {
      return
    }

    if (isDef(vnode.elm) && isDef(ownerArray)) {
      // clone reused vnode
      vnode = ownerArray[index] = cloneVNode(vnode)
    }
    // Take the real DOM first
    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
    }

    // // Check whether all nodes are static
    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)
    }
    / / child nodes
    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)
    }
    // Check if there is a text attribute, if not
    if (isUndef(vnode.text)) {
      // Whether there are child nodes
      if (isDef(oldCh) && isDef(ch)) {
        // If there are child nodes, check whether the child nodes are the same. If they are different, update the child nodes
        if(oldCh ! == ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) }else if (isDef(ch)) { // If only vNodes have child nodes

        if(process.env.NODE_ENV ! = ='production') {
          checkDuplicateKeys(ch)
        }
        /* Determine if oldVNode has text. If so, empty the text and add the child node to the DOM */
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, ' ')
        // Add the child node to the DOM
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
      } else if (isDef(oldCh)) {
        /* oldVNode has child nodes, but VNode has no child nodes
        removeVnodes(oldCh, 0, oldCh.length - 1)}else if (isDef(oldVnode.text)) {
        /* Both VNode and oldVNode have no child nodes, but oldVNode has text to empty oldNode text */
        nodeOps.setTextContent(elm, ' ')}}else if(oldVnode.text ! == vnode.text) {// Is the text attribute of vNode the same as the text attribute of oldVnode?
      // If not, VNode's text replaces DOM's text directly
      // 
      nodeOps.setTextContent(elm, vnode.text)
    }
    if (isDef(data)) {
      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
    }
  }
Copy the code

Update child nodes

This needs to be taken out separately

If both have children and the children are different, the updateChildren function is required

updateChildren

/* When both VNode and oldVNode have children and are not equal, execute this function
  function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0 /* oldChildren start indexing */
    // // newChildren start indexing
    let newStartIdx = 0 / * * /
    let oldEndIdx = oldCh.length - 1 /* oldChildren end index */
    // newChildren ends the index
    let newEndIdx = newCh.length - 1
    // The first of all unprocessed nodes in oldChildren
    let oldStartVnode = oldCh[0]
    // Last of all unprocessed nodes in oldChildren
    let oldEndVnode = oldCh[oldEndIdx]
    // The first of all unprocessed nodes in newChildren
    let newStartVnode = newCh[0]
    // Last of all unprocessed nodes in newChildren
    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
    constcanMove = ! removeOnlyif(process.env.NODE_ENV ! = ='production') {
      checkDuplicateKeys(newCh)
    }
    /* oldStartIdx: start index of the old virtual node oldEndIdx: END index of the old virtual node newStartIdx: start index of the new virtual node newEndIdx: start index of the new virtual node */
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (isUndef(oldStartVnode)) {
        /* If oldStartVnode is undefined, the pointer moves backwards */
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
      } else if (isUndef(oldEndVnode)) {
        /* oldEndVnode moves forward if undefined */
        oldEndVnode = oldCh[--oldEndIdx]
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
        /* The new VNode is the same as the old VNode, so update the child node and move the pointer back */
        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)
        /* Move the old front to the old back */
        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)
        /* Move the old back to the old front */
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
      } else {
        // If it does not fall under the above four conditions, the patch will be cyclically compared
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)

        /* This is not a loop, but a map */
        idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key]
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)

        if (isUndef(idxInOld)) { 
          // If oldChildren cannot be found in newChildren
          // Add a new node and insert it into the appropriate position
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
        } else {
          // If the child node is found
          vnodeToMove = oldCh[idxInOld]
          if (sameVnode(vnodeToMove, newStartVnode)) {
            // If the two nodes are the same, the child node is updated
            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
            oldCh[idxInOld] = undefined
            /* Move the old node to the front of the old node */
            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
          } else {
            // same key but different element. treat as new element
            // If not, create the node and insert it directly
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
          }
        }
        // newStartIdx moves the pointer back
        newStartVnode = newCh[++newStartIdx]
      }
    }
    if (oldStartIdx > oldEndIdx) {
      /* If oldChildren first completes the loop and inserts all nodes between [newStartIdx, newEndIdx] into the DOM */
      refElm = isUndef(newCh[newEndIdx + 1])?null : newCh[newEndIdx + 1].elm
      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else if (newStartIdx > newEndIdx) {
      /* Delete all nodes between [oldStartIdx, oldEndIdx] */
      removeVnodes(oldCh, oldStartIdx, oldEndIdx)
    }
  }
Copy the code