directory

1. Tool function 2. Diff process 3Copy the code

Time for the exciting diff segment

Using the framework of Virtual Dom, the general design idea is that the page equals the mapping of the page state, that is, UI = render(state).

Changes to the real DOM during page updates can be compared in memory using the Virtual DOM, also known as diff.

The Virtual Dom in Vue2 is a VNode

export default class VNode {
  tag: string | void;
  data: VNodeData | void;
  children: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void; // rendered in this component's scope
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node

  // strictly internal. }Copy the code

Tool function

Before we begin, let’s briefly describe the functions of a few utility functions

export function isUndef(v: any) :boolean {
  return v === undefined || v === null;
}
Copy the code

Determines whether a variable is undefined, that is, equal to undefined or null

export function isDef(v: any) :boolean {
  returnv ! = =undefined&& v ! = =null;
}
Copy the code

Determines whether a variable is defined, that is, not equal to undefined and null

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)) ||
      (isTrue(a.isAsyncPlaceholder) && isUndef(b.asyncFactory.error)))
  );
}
Copy the code

By comparing the key, tag, and inputType, you can check whether the two nodes are of the same type and reusable

function patchVnode(oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
  // step 1
  if (oldVnode === vnode) {
    return;
  }

  // step 2
  if (isDef(vnode.elm) && isDef(ownerArray)) {
    vnode = ownerArray[index] = cloneVNode(vnode);
  }

  // step 3
  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;
  }

  // step 4
  if (
    isTrue(vnode.isStatic) &&
    isTrue(oldVnode.isStatic) &&
    vnode.key === oldVnode.key &&
    (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
  ) {
    vnode.componentInstance = oldVnode.componentInstance;
    return;
  }

  // step 5
  let i;
  const data = vnode.data;
  if (isDef(data) && isDef((i = data.hook)) && isDef((i = i.prepatch))) {
    i(oldVnode, vnode);
  }

  // step 6
  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);
  }
  // step 7
  if (isUndef(vnode.text)) {
    if (isDef(oldCh) && isDef(ch)) {
      // step 7-1
      if(oldCh ! == ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); }else if (isDef(ch)) {
      // step 7-2
      if(process.env.NODE_ENV ! = ="production") {
        checkDuplicateKeys(ch);
      }
      // step 7-3
      if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, "");
      // step 7-4
      addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
    } else if (isDef(oldCh)) {
      // step 7-5
      removeVnodes(oldCh, 0, oldCh.length - 1);
    } else if (isDef(oldVnode.text)) {
      // step 7-6
      nodeOps.setTextContent(elm, ""); }}else if(oldVnode.text ! == vnode.text) {// step 8
    nodeOps.setTextContent(elm, vnode.text);
  }
  // step 9
  if (isDef(data)) {
    if(isDef((i = data.hook)) && isDef((i = i.postpatch))) i(oldVnode, vnode); }}Copy the code
  • Step 1: If the old and new nodes are the same, return directly without modification
  • Step 2: If the elm attribute of the virtual node exists, it indicates that it has been rendered. If the ownerArray exists, it indicates that there are child nodes. If these two points are true, clone a VNode node
  • Step 3: For asynchronous placeholder, execute the Hydrate method or define isAsyncPlaceholder to true and exit
  • Return the componentInstance attribute if the following four conditions are met to indicate that the component is still the same as the previous instance
    • A Vnode is a static node
    • OldVnode is a static node
    • The key attributes are all equal
    • A vnode is a virtual DOM clone or a component that is rendered only once (V-once)
  • Define data constants, on which the following properties are typically defined: attrs, ON events, directives, props, hook hooks. This calls the hook function of prepatch
  • The next step is to call various update functions.
    • updateAttrsUpdate the attR attribute
    • updateClassUpdate the class attribute
    • updateDOMListenersUpdate binding event properties
    • updateDOMPropsUpdating the props property
    • updateStyleUpdate the style attribute
    • updateIf the ref attribute exists, update according to the ref attribute
    • updateDrectivesUpdated the Drectives attribute
  • Step 7: Check whether text exists
    • Call if the old vnode is different from the new vnodeupdateChildrenFunction to update
    • Step 7-2: In a non-production environment, check whether duplicate keys exist. If duplicate keys exist, a message is displayed
    • If the old vNode does not have a subset but does have a text attribute, and the new vnode does have a subset, clear the text attribute
    • Step 7-4: Add new nodes
    • Step 7-5: Remove child nodes
    • Step 7-6: Set the display text to empty
  • Step 8: Set the display text to the latest value
  • Step 9: Execute data.hook.postpatch hook, indicating that patch is complete

The diff process

Dom diff occurs before a view is updated to improve framework performance, and we are talking about diff for peer nodes

// src/core/vdom/patch.js
function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
  // Initialize variables
  let oldStartIdx = 0;
  let oldEndIdx = oldCh.length - 1;
  let oldStartVnode = oldCh[0];
  let oldEndVnode = oldCh[oldEndIdx];

  let newStartIdx = 0;
  let newEndIdx = newCh.length - 1;
  let newStartVnode = newCh[0];
  let newEndVnode = newCh[newEndIdx];

  letoldKeyToIdx, idxInOld, vnodeToMove, refElm; ./ / on the whole
  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (isUndef(oldStartVnode)) {
      // step 1: The current oldStartVnode is undefined or null
      oldStartVnode = oldCh[++oldStartIdx]; // oldStartIdx moves to the right
    } else if (isUndef(oldEndVnode)) {
      // step 2:当前 oldEndVnode 为 undefined 或 null
      oldEndVnode = oldCh[--oldEndIdx]; // oldEndIdx moves to the left
    } else if (sameVnode(oldStartVnode, newStartVnode)) {
      // Step 3: oldStartVnode is the same as newStartVnode. Copy the example. If there are child nodes, proceed with the child nodes
      patchVnode(
        oldStartVnode,
        newStartVnode,
        insertedVnodeQueue,
        newCh,
        newStartIdx
      );
      // oldStartIdx and newStartIdx move to the right
      oldStartVnode = oldCh[++oldStartIdx];
      newStartVnode = newCh[++newStartIdx];
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
      // Step 4: Compare oldEndVnode and newEndVnode, copy the example, if there are child nodes, continue to process the child nodes
      patchVnode(
        oldEndVnode,
        newEndVnode,
        insertedVnodeQueue,
        newCh,
        newEndIdx
      );
      // oldEndIdx and newEndIdx move to the left
      oldEndVnode = oldCh[--oldEndIdx];
      newEndVnode = newCh[--newEndIdx];
    } else if (sameVnode(oldStartVnode, newEndVnode)) {
      // Step 5: Start to cross-compare oldStartVnode and newEndVnode
      // If they are the same, copy the examples. If there are children, continue processing the children
      patchVnode(
        oldStartVnode,
        newEndVnode,
        insertedVnodeQueue,
        newCh,
        newEndIdx
      );
      // If it can be moved
      // Move the real node corresponding to oldStartVnode right to the end of the current real node queue
      // (the position after the current oldEndVnode)
      canMove &&
        nodeOps.insertBefore(
          parentElm,
          oldStartVnode.elm,
          nodeOps.nextSibling(oldEndVnode.elm)
        );
      oldStartVnode = oldCh[++oldStartIdx]; // oldStartIdx moves to the right
      newEndVnode = newCh[--newEndIdx]; // newEndIdx moves left
    } else if (sameVnode(oldEndVnode, newStartVnode)) {
      // Step 6: Cross-compare oldEndVnode and newStartVnode
      // If there are children, continue to process them
      patchVnode(
        oldEndVnode,
        newStartVnode,
        insertedVnodeQueue,
        newCh,
        newStartIdx
      );
      // It can be moved
      // Move the real node corresponding to oldEndVnode left to the head of the real node queue
      // (the previous location of the current oldStartVnode)
      canMove &&
        nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
      oldEndVnode = oldCh[--oldEndIdx];
      newStartVnode = newCh[++newStartIdx];
    } else {
      if (isUndef(oldKeyToIdx))
        // Step 7: Run through the old VD queue to generate a Map of key-ID
        oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
      / / step 8:
      // Check whether the new VD has a key first
      // If there is a key, use oldKeyToIdx to find if there is a node with the same key
      // If it does not exist, findIdxInOld is called to traverse the old VD queue through sameVnode to find subscripts corresponding to VD of the same type
      idxInOld = isDef(newStartVnode.key)
        ? oldKeyToIdx[newStartVnode.key]
        : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
      if (isUndef(idxInOld)) {
        // Step 9: If no consistent node is found in the old VD, it is a new node
        createElm(
          newStartVnode,
          insertedVnodeQueue,
          parentElm,
          oldStartVnode.elm,
          false,
          newCh,
          newStartIdx
        );
      } else {
        // Nodes with the same key value or type exist
        vnodeToMove = oldCh[idxInOld];
        // For nodes with the same key value, check whether the node types are the same
        if (sameVnode(vnodeToMove, newStartVnode)) {
          // Step 10: Keep the keys and node types the same
          // Copy the example and continue processing its children if there are any
          patchVnode(
            vnodeToMove,
            newStartVnode,
            insertedVnodeQueue,
            newCh,
            newStartIdx
          );
          // The node is in use and set to undefined
          oldCh[idxInOld] = undefined;
          // If movement is allowed, the found reusable old nodes are moved to the head of the real Dom queue
          canMove &&
            nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm);
        } else {
          // Step 11: The keys are the same, but the node types are different
          createElm(
            newStartVnode,
            insertedVnodeQueue,
            parentElm,
            oldStartVnode.elm,
            false,
            newCh,
            newStartIdx
          );
        }
      }
      newStartVnode = newCh[++newStartIdx]; // newStartIdx moves to the right}}// The loop ends
  // If oldStartIdx is greater than oldEndIdx, oldStartIdx is behind oldEndIdx
  // Indicates that the old VD queue is traversed
  if (oldStartIdx > oldEndIdx) {
    refElm = isUndef(newCh[newEndIdx + 1])?null : newCh[newEndIdx + 1].elm;
    // Step 12: Iterate over the remaining new VD and create it
    addVnodes(
      parentElm,
      refElm,
      newCh,
      newStartIdx,
      newEndIdx,
      insertedVnodeQueue
    );
  } else if (newStartIdx > newEndIdx) {
    // If newStartIdx is greater than newEndIdx
    // The new VD is traversed, and the old VD is left
    // Delete the remaining old VDSremoveVnodes(oldCh, oldStartIdx, oldEndIdx); }}Copy the code

The illustration

The following figure illustrates the diff process of peer nodes step by step

Start the first diff

As shown in the figure above, oldStartVnode and newStartVnode are of the same type. Enter Step 3 and reuse node A. OldStartIdx and newStartIdx move to the right

The second diff starts, as shown in the figure above. OldEndVnode and newEndVnode are of the same type. Step 4 is entered

Then the third round of diff begins. OldStartVnode and newEndVnode have the same node type. Step 5, node B is re-used, and node B moves to the real node corresponding to node E (oldEndVnode), oldStartIdx moves to the right. NewEndIdx moves to the left

As shown in the figure above, the fourth round of diff begins. The node type of newStartVnode is the same as that of oldEndVnode. Step 6, node E is multiplexed, and node E moves to the real node corresponding to node C (oldStartVnode). NewStartIdx moves to the right, oldEndIdx moves to the left

As shown in the figure above, to start the next diff, oldStartVnode, oldEndVnode, newStartVnode, and newEndVnode do not have nodes of the same type

  • Step 7: The Old VD queue that has not been processed is traversedoldKeyToIdx = {c:2, d:3, f:4}.
  • Step 8: JudgenewStartVnodeIs there a key? HerenewStartVnodeThe key value isH, so start looking for the previous stepoldKeyToIdxIs there any key with the same value in
  • If the node does not have the same key value, the node is identified as a new node. Step 9: Create a nodeHAnd insert intooldStartVnodeBefore the node (createElmMethod implementation)
  • newStartIdxTo the right

After the previous step, oldStartVnode and newStartVnode are the same again, as shown in the figure above. Continue step 3, oldStartIdx and newStartIdx continue to move to the right

In this case, both newStartIdx and newEndIdx point to node D. OldStartVnode and newStartVnode are of the same type. Therefore, oldStartIdx and newStartIdx continue to move to the right

At this point, newStartIdx has moved behind newEndIdx, newStartIdx is greater than newEndIdx, so break out of the while loop and go step 13: delete the remaining Old VD, the F node here

At this point, diff is complete

conclusion

The article is also posted on my official account, welcome to follow MelonField

reference

  • github.com/vuejs/vue