What is the virtual DOM

Virtual DOM uses JavaScript objects to describe the DOM. The essence of Virtual DOM is JavaScript objects, which are used to describe the structure of the DOM. Application state changes first act on the virtual DOM and eventually map to the DOM. The virtual DOM in vue. js borrows from Snabbdom and adds some features from vue. js, such as directives and component mechanisms.

For the changes of fine-grained monitoring data in Vue 1.x, each attribute corresponds to a watcher, which is too expensive. In Vue 2.x, each component corresponds to a Watcher. The status change is notified to the component, and then virtual DOM is introduced for comparison and rendering.

Why use the virtual DOM

  • Using virtual DOM, users can avoid directly operating the DOM, and the development process focuses on the implementation of business code, rather than how to operate the DOM, so as to improve the development efficiency.
  • As an intermediate layer can be cross-platform, in addition to the Web platform, also support SSR, Weex.
  • In terms of performance, the first rendering is definitely not as good as using the DOM directly, because there is an extra layer of virtual DOM to maintain, and if there are subsequent frequent DOM operations, there may be performance gains. The virtual DOM compares the differences between the old and new virtual DOM trees by the Di o ff algorithm before updating the real DOM, eventually updating the differences to the real DOM

The virtual DOM in vue.js

  • Demonstrate the h function in render
    • The h function is createElement()
const vm = new Vue({
  el: '#app',
  render (h) {
    // h(tag, data, children)
    // return h('h1', this.msg)
    // return h('h1', { domProps: { innerHTML: this.msg } })
    // return h('h1', { attrs: { id: 'title' } }, this.msg)
    const vnode = h(
      'h1',
      {
        attrs: { id: 'title'}},this.msg
    )
    console.log(vnode)
    return vnode
  },
  data: {
    msg: 'Hello Vue'}})Copy the code

H function

vm.$createElement(tag,data,children,normalizeChildren)

  • Tag: indicates the tag name or component name
  • Data: Describes the tag. You can set DOM attributes or tag attributes
  • Children: Tag-heavy text content or child nodes

Virtual DOM creation process

createElement

The function createElement() is used to create the virtual node (VNode). The parameter h in the render function is createElement().

render(h) {
  $createElement = vm.$createElement
  return h('h1'.this.msg)
}
Copy the code

A definition in vm._render() calls a user-passed or compiled render function, passing createElement

  • src/core/instance/render.js
// The method to render the generated render
vm._c = (a, b, c, d) = > createElement(vm, a, b, c, d, false)
  
// The method of writing the render function
vm.$createElement = (a, b, c, d) = > createElement(vm, a, b, c, d, true)
Copy the code

Both vm.c and VM.$createElement call createElement internally, with the exception of the last argument. Vm. c is called inside the generated render function, and vm.$createElement is called inside the user’s passed render function. When the user passes in the render function, the parameter is processed by the user

  • src/core/vdom/create-element.js

After executing createElement, the VNode is created and passed to vm._update() to continue processing

export function createElement(context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean) :VNode | Array<VNode> {
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children;
    children = data;
    data = undefined;
  }
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE;
  }
  return _createElement(context, tag, data, children, normalizationType);
}

export function _createElement(context: Component, tag? : string | Class<Component> |Function | Object, data? : VNodeData, children? : any, normalizationType? : number) :VNode | Array<VNode> {
  // Determine the third parameter
  // Children if data is an array or a primitive value
  if(isDef(data) && isDef((data: any).__ob__)) { process.env.NODE_ENV ! = ="production" &&
      warn(
        `Avoid using observed data object as vnode data: The ${JSON.stringify(
          data
        )}\n` + "Always create fresh vnode data objects in each render!",
        context
      );
    return createEmptyVNode();
  }
  // object syntax in v-bind
  if (isDef(data) && isDef(data.is)) {
    tag = data.is;
  }
  if(! tag) {// in case of component :is set to falsy value
    return createEmptyVNode();
  }
  // warn against non-primitive key
  if( process.env.NODE_ENV ! = ="production"&& isDef(data) && isDef(data.key) && ! isPrimitive(data.key) ) {if(! __WEEX__ || ! ("@binding" in data.key)) {
      warn(
        "Avoid using non-primitive value as key, " +
          "use string/number value instead.", context ); }}// support single function children as default scoped slot
  if (Array.isArray(children) && typeof children[0= = ="function") {
    data = data || {};
    data.scopedSlots = { default: children[0]}; children.length =0;
  }
  // To handle children
  if (normalizationType === ALWAYS_NORMALIZE) {
    // Called when the render function is hand-written
    // Determine the type of children and convert it to an array of VNodes if it is a primitive value
    // If it is an array, continue processing the elements in the array
    // If the child elements in the array are also arrays (slot templates), recursively
    // If two consecutive nodes are strings, the text nodes will be merged
    children = normalizeChildren(children);
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    // Convert a nested array to a one-dimensional array
    // Render the template
    // If there is a function component in children, the function component returns an array
    // In this case, children is a two-dimensional array. We only need to convert the two-dimensional array to a one-dimensional array
    children = simpleNormalizeChildren(children);
  }
  let vnode, ns;
  // Determine whether the tag is a string or a component
  if (typeof tag === "string") {
    let Ctor;
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag);
    // If it is a reserved label for the browser, create the corresponding VNode
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      if( process.env.NODE_ENV ! = ="production" &&
        isDef(data) &&
        isDef(data.nativeOn)
      ) {
        warn(
          `The .native modifier for v-on is only valid on components but it was used on <${tag}>. `,
          context
        );
      }
      vnode = new VNode(
        config.parsePlatformTagName(tag),
        data,
        children,
        undefined.undefined,
        context
      );
      // Check whether it is a custom component
    } else if((! data || ! data.pre) && isDef((Ctor = resolveAsset(context.$options,"components", tag)))
    ) {
      // Find the declaration of the custom component constructor
      // Create vNodes for components based on Ctor
      // component
      vnode = createComponent(Ctor, data, context, children, tag);
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      vnode = new VNode(tag, data, children, undefined.undefined, context); }}else {
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children);
  }
  if (Array.isArray(vnode)) {
    return vnode;
  } else if (isDef(vnode)) {
    if (isDef(ns)) applyNS(vnode, ns);
    if (isDef(data)) registerDeepBindings(data);
    return vnode;
  } else {
    returncreateEmptyVNode(); }}Copy the code

update

function

An internal call to vm.__patch__() converts the virtual DOM to the real DOM definition

  • src/core/instance/lifecycle.js
// The _update method renders VNode as a real DOM
// The first render is called, and the data update is called
Vue.prototype._update = function (vnode: VNode, hydrating? : boolean) {
  const vm: Component = this
  const prevEl = vm.$el
  const prevVnode = vm._vnode
  const restoreActiveInstance = setActiveInstance(vm)
  vm._vnode = vnode
  // Vue.prototype.__patch__ is injected in entry points
  // based on the rendering backend used.
  if(! prevVnode) {// initial render
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)}else {
    // updates
    vm.$el = vm.__patch__(prevVnode, vnode)
  }
  restoreActiveInstance()
  // update __vue__ reference
  if (prevEl) {
    prevEl.__vue__ = null
  }
  if (vm.$el) {
    vm.$el.__vue__ = vm
  }
  // if parent is an HOC, update its $el as well
  if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
    vm.$parent.$el = vm.$el
  }
  // updated hook is called by the scheduler to ensure that children are
  // updated in a parent's updated hook.
}
Copy the code

The patch function is initialized

function

Compare the differences between the two VNodes and update the differences to the real DOM. For the first rendering, the real DOM will be converted to VNode first

Initialization of patch function in Snabbdom

  • src/snabbdom.ts
export function init (modules: Array<Partial<Module>>, domApi? : DOMAPI) {
  return function patch (oldVnode: VNode | Element, vnode: VNode) :VNode {}}Copy the code
  • vnode
export function vnode (sel: string | undefined,
  data: any | undefined,
  children: Array<VNode | string> | undefined,
  text: string | undefined,
  elm: Element | Text | undefined) :VNode {
  const key = data === undefined ? undefined : data.key
  return { sel, data, children, text, elm, key }
}
Copy the code

Initialize the patch function in vue. js

  • src/platforms/web/runtime/index.js
import { patch } from './patch'
If yes, return patch; otherwise, return null
Vue.prototype.__patch__ = inBrowser ? patch : noop
Copy the code
  • src/platforms/web/runtime/patch.js
import * as nodeOps from 'web/runtime/node-ops'
import { createPatchFunction } from 'core/vdom/patch'
import baseModules from 'core/vdom/modules/index'
import platformModules from 'web/runtime/modules/index'

// the directive module should be applied last, after all
// built-in modules have been applied.
const modules = platformModules.concat(baseModules)

export const patch: Function = createPatchFunction({ nodeOps, modules })
Copy the code
  • src/core/vdom/patch.js
export function createPatchFunction (backend) {
  let i, j
  const cbs = {}
  const { modules, nodeOps } = backend
  // set all the hook functions in the module to CBS
  // cbs --> { 'create': [fn1, fn2], ... }
  for (i = 0; i < hooks.length; ++i) {
    cbs[hooks[i]] = []
    for (j = 0; j < modules.length; ++j) {
      if(isDef (modules [j] [hooks [I]])) {CBS [hooks [I]]. Push (modules) [j] [hooks [I]])}}}... ... ...return function patch (oldVnode, vnode, hydrating, removeOnly) {}}Copy the code

Patch function execution procedure

function patch (oldVnode, vnode, hydrating, removeOnly) {
  // The new VNode does not exist
  if (isUndef(vnode)) {
    // The old VNode exists. Execute the destruct hook function to delete the old VNode
    if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
    return
  }

  let isInitialPatch = false
  const insertedVnodeQueue = []

  // Old VNode does not exist
  if (isUndef(oldVnode)) {
    // empty mount (likely as component), create new root element
    isInitialPatch = true
    // Create a new VNode that corresponds to the real DOM
    createElm(vnode, insertedVnodeQueue)
  } else {
    // New and old vNodes exist, update
    const isRealElement = isDef(oldVnode.nodeType)
    // Check whether parameter 1 is a real DOM, not a real DOM
    if(! isRealElement && sameVnode(oldVnode, vnode)) {// Update the operation, diff algorithm
      // patch existing root node
      patchVnode(oldVnode, vnode, insertedVnodeQueue, null.null, removeOnly)
    } else {
      // The first parameter is the real DOM, creating the VNode
      / / initialization
      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
      // Create a DOM 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
}
Copy the code

createElm

Convert VNode to real DOM and insert it into the DOM tree

function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
  if (isDef(vnode.elm) && isDef(ownerArray)) {
    // This vnode was used in a previous render!
    // now it's used as a new node, overwriting its elm would cause
    // potential patch errors down the road when it's used as an insertion
    // reference node. Instead, we clone the node on-demand before creating
    // associated DOM element for it.vnode = ownerArray[index] = cloneVNode(vnode) } vnode.isRootInsert = ! nested// for transition enter check
  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
    return
  }

  const data = vnode.data
  const children = vnode.children
  const tag = vnode.tag
  if (isDef(tag)) {
    if(process.env.NODE_ENV ! = ='production') {
      if (data && data.pre) {
        creatingElmInVPre++
      }
      if (isUnknownElement(vnode, creatingElmInVPre)) {
        warn(
          'Unknown custom element: <' + tag + '> - did you ' +
          'register the component correctly? For recursive components, ' +
          'make sure to provide the "name" option.',
          vnode.context
        )
      }
    }

    vnode.elm = vnode.ns
      ? nodeOps.createElementNS(vnode.ns, tag)
      : nodeOps.createElement(tag, vnode)
    setScope(vnode)

    /* istanbul ignore if */
    if (__WEEX__) {
      // in Weex, the default insertion order is parent-first.
      // List items can be optimized to use children-first insertion
      // with append="tree".
      const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
      if(! appendAsTree) {if (isDef(data)) {
          invokeCreateHooks(vnode, insertedVnodeQueue)
        }
        insert(parentElm, vnode.elm, refElm)
      }
      createChildren(vnode, children, insertedVnodeQueue)
      if (appendAsTree) {
        if (isDef(data)) {
          invokeCreateHooks(vnode, insertedVnodeQueue)
        }
        insert(parentElm, vnode.elm, refElm)
      }
    } else {
      createChildren(vnode, children, insertedVnodeQueue)
      if (isDef(data)) {
        invokeCreateHooks(vnode, insertedVnodeQueue)
      }
      insert(parentElm, vnode.elm, refElm)
    }
    if(process.env.NODE_ENV ! = ='production' && data && data.pre) {
      creatingElmInVPre--
    }
  } else if (isTrue(vnode.isComment)) {
    vnode.elm = nodeOps.createComment(vnode.text)
    insert(parentElm, vnode.elm, refElm)
  } else {
    vnode.elm = nodeOps.createTextNode(vnode.text)
    insert(parentElm, vnode.elm, refElm)
  }
}
Copy the code

patchVnode

  function patchVnode(oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
    // If the new and old nodes are exactly the same, return directly
    if (oldVnode === vnode) {
      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 both new and old VNodes are static, replace componentInstance
    if (
      isTrue(vnode.isStatic) &&
      isTrue(oldVnode.isStatic) &&
      vnode.key === oldVnode.key &&
      (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
    ) {
      vnode.componentInstance = oldVnode.componentInstance;
      return;
    }
    // Triggers the prepatch hook function
    let i;
    const data = vnode.data;
    if (isDef(data) && isDef((i = data.hook)) && isDef((i = i.prepatch))) {
      i(oldVnode, vnode);
    }
    // Obtain the child nodes of the new and old VNode
    const oldCh = oldVnode.children;
    const ch = vnode.children; 
    // Fires the update hook function
    if (isDef(data) && isPatchable(vnode)) {
      // Call the hook function in CBS to operate on the properties/styles/events of the node....
      for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode);
      // User's custom hooks
      if (isDef((i = data.hook)) && isDef((i = i.update))) i(oldVnode, vnode);
    }

    // The new node has no text
    if (isUndef(vnode.text)) {
      // Both the old node and the old node have children
      // Diff the child node and call updateChildren
      if (isDef(oldCh) && isDef(ch)) {
        if(oldCh ! == ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); }else if (isDef(ch)) {
        // The new one has children; the old one has no children
        if(process.env.NODE_ENV ! = ="production") {
          checkDuplicateKeys(ch);
        }
        // Clean up the text content of the old DOM node, then add child nodes to the current DOM node
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, "");
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
      } else if (isDef(oldCh)) {
        // The old node has children and the new node has no children
        // Delete the child node from the old node
        removeVnodes(oldCh, 0, oldCh.length - 1);
      } else if (isDef(oldVnode.text)) {
        // The old node has text and the new node has no text
        // Clear the text content of the old node
        nodeOps.setTextContent(elm, ""); }}else if(oldVnode.text ! == vnode.text) {// Both old and new nodes have text nodes
      // Modify the text
      nodeOps.setTextContent(elm, vnode.text);
    }
    if (isDef(data)) {
      if(isDef((i = data.hook)) && isDef((i = i.postpatch))) i(oldVnode, vnode); }}Copy the code

updateChildren

The function of updateChildren is to compare the new Parent node, find the differences between the children nodes, update them to the DOM tree, and reuse the node if it hasn’t changed.

  / / diff algorithm
  // Update the children of the new and old nodes
  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
    constcanMove = ! removeOnly;if(process.env.NODE_ENV ! = ="production") {
      checkDuplicateKeys(newCh);
    }
    / / diff algorithm
    // When both the old and new nodes have not been traversed
    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)) {
        // oldStartVnode is the same as newStartVnode (sameVnode)
        // Run the patchVnode command on the VNode
        patchVnode(
          oldStartVnode,
          newStartVnode,
          insertedVnodeQueue,
          newCh,
          newStartIdx
        );
        // Get the next set of start nodes
        oldStartVnode = oldCh[++oldStartIdx];
        newStartVnode = newCh[++newStartIdx];
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // Run the patchVnode command on the VNode
        // Run the patchVnode command on the VNode
        patchVnode(
          oldEndVnode,
          newEndVnode,
          insertedVnodeQueue,
          newCh,
          newEndIdx
        );
        // Get the next set of end nodes
        oldEndVnode = oldCh[--oldEndIdx];
        newEndVnode = newCh[--newEndIdx];
      } else if (sameVnode(oldStartVnode, newEndVnode)) {
        // Vnode moved right
        OldStartVnode is the same as newEndVnode (sameVnode)
        // Run patchVnode and move oldStartVnode to the end
        patchVnode(
          oldStartVnode,
          newEndVnode,
          insertedVnodeQueue,
          newCh,
          newEndIdx
        );
        canMove &&
          nodeOps.insertBefore(
            parentElm,
            oldStartVnode.elm,
            nodeOps.nextSibling(oldEndVnode.elm)
          );
        // Move the cursor to get the next set of nodes
        oldStartVnode = oldCh[++oldStartIdx];
        newEndVnode = newCh[--newEndIdx];
      } else if (sameVnode(oldEndVnode, newStartVnode)) {
        // Vnode moved left
        OldEndVnode is the same as newStartVnode (sameVnode)
        // Run patchVnode and move oldEndVnode to the front
        patchVnode(
          oldEndVnode,
          newStartVnode,
          insertedVnodeQueue,
          newCh,
          newStartIdx
        );
        canMove &&
          nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
        oldEndVnode = oldCh[--oldEndIdx];
        newStartVnode = newCh[++newStartIdx];
      } else {
        // None of the above four situations is satisfied
        // newStartNode is compared to the old node

        // Get one from the beginning of the new node and go to the old node to find the same node
        // Look for the new start node with the same key as the old start node, and if you can't find it, look for it through sameVnode
        if (isUndef(oldKeyToIdx))
          oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
        idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key]
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
        // If not found
        if (isUndef(idxInOld)) {
          // New element
          // Create the node and insert it to the front
          createElm(
            newStartVnode,
            insertedVnodeQueue,
            parentElm,
            oldStartVnode.elm,
            false,
            newCh,
            newStartIdx
          );
        } else {
          // Get the old node to move
          vnodeToMove = oldCh[idxInOld];
          // If newStartNode is used to find the same old node
          if (sameVnode(vnodeToMove, newStartVnode)) {
            // Execute patchVnode and move the old node found to the front
            patchVnode(
              vnodeToMove,
              newStartVnode,
              insertedVnodeQueue,
              newCh,
              newStartIdx
            );
            oldCh[idxInOld] = undefined;
            canMove &&
              nodeOps.insertBefore(
                parentElm,
                vnodeToMove.elm,
                oldStartVnode.elm
              );
          } else {
            // If the key is the same, but the element is different, create a new element
            // same key but different element. treat as new element
            createElm(
              newStartVnode,
              insertedVnodeQueue,
              parentElm,
              oldStartVnode.elm,
              false, newCh, newStartIdx ); } } newStartVnode = newCh[++newStartIdx]; }}// When oldStartIdx > oldEndIdx is finished, the old node has been traversed, but the new node has not
    if (oldStartIdx > oldEndIdx) {
      // Specify that there are more new nodes than old ones, insert the remaining new nodes after the old ones
      refElm = isUndef(newCh[newEndIdx + 1])?null : newCh[newEndIdx + 1].elm;
      addVnodes(
        parentElm,
        refElm,
        newCh,
        newStartIdx,
        newEndIdx,
        insertedVnodeQueue
      );
    } else if (newStartIdx > newEndIdx) {
      // When finished newStartIdx > newEndIdx, the new node has been traversed, but the old node has not
      // When finished newStartIdx > newEndIdx, the new node has been traversed, but the old node has notremoveVnodes(oldCh, oldStartIdx, oldEndIdx); }}Copy the code

In patch function, before calling patchVnode, sameVnode() will first be called to determine whether the current new and old vnodes are the same, and sameVnode() will first determine whether the key is the same.

  • To see what a key does:
<div id="app">
  <button @click="handler">button</button>
  <ul>
    <li v-for="value in arr">{{value}}</li>
  </ul>
</div>

<script src=".. /.. /dist/vue.js"></script>
<script>
  const vm = new Vue({
    el: '#app'.data: {
      arr: ['a'.'b'.'c'.'d']},methods: { 
      handler () {
        this.arr = ['a'.'x'.'b'.'c'.'d']}}})</script>
Copy the code
  • When the key is not set

When you compare child nodes in updateChildren, you do three DOM updates and one DOM insert

  • When you set the key

When comparing child nodes in updateChildren, because b, C,d of oldVnode’s child nodes have the same key as x,b,c of newVnode, so only comparison is performed without DOM update operation. When the traversal is completed, I’m going to insert x into the DOM again and DOM only does one insert.