How does the diff algorithm of Vue work? This article teaches you

Keywords: Javascript Vue React


This paper aims at the main logic and key details of diff algorithm in vue.

Starting from a simple demo: p tag renders an array of items

<div id="demo">
    <p v-for="item in items" :key="item">{{ item }}</p>
<script src="../vue-source/dist/vue.js"></script>
    const app = new Vue({
      el: "#demo",
      data: {
        items: ["a", "b", "c", "d", "e"]
      mounted() {
        setTimeout(() => {
          this.items.splice(2, 0, "f")
        }, 2000)
Copy code

First, explain the actual sequence:

  1. Item data changes Dep.notify
  2. patch(oldVNode, vnode, ...)
  3. Patch vnode (oldvnode, vnode, insertedVnodeQueue,...) PS: diff started from here. insertedVnodeQueue is a constant defined in the patch function, which has been maintained in the diff in the later period. It is a typical closure structure.
  4. The core method of updateChildren() diff
    5. If you want to learn to practice together with the actual combat, you can get the latest enterprise level Vue3.0/Js/ES6/TS/React/node and other real combat video tutorials in 2020. You can get them in 519293536 for free, and you can't get them in Xiaobai!


sameVnode function runs through the whole diff process, and the first necessary condition is that the key must be equal

function sameVnode (a, b) {
  return (
    a.key === b.key && (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef( === isDef( &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
Copy code


As we all know, key plays an important role in patch. Key can effectively reduce unnecessary re rendering in many cases. When the key is not set, the sub element key in the rendering list data is undefined, obviously undefined === undefined. sameVNode is always the same (usually), and it will cause unnecessary rendering (if the key is not set in the first demo, it will cause unnecessary rendering 3 times more).

If the key is set, a.key! = = b.key, the judgment will be terminated immediately. sameVnode directly returns false instead of bb.
Avoid using array subscripts as key s
Because when the array changes, the subscript may also change, which may lead to some hidden bug s.


  • If there is no oldVnode, create elm
  • oldVnode and vnode exist, but sameVnode returns false, then createElm
  • oldVnode and vnode exist, but sameVnode returns true, patchVnode


Vnode can be divided into three types:

  • Plain text Vnode
  • Vnode with Children
  • Vnode without Children

So it can be divided into 3 * 3 situations

  oldVnode.text oldCh !oldCh
vnode.text setTextContent setTextContent setTextContent
ch addVnodes updateChildren addVnodes
!ch setTextContent removeVnodes setTextContent
function patchVnode (oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
  // If the nodes are the same, they will be returned directly without processing
  if (oldVnode === vnode) {
  // ...
  const elm = vnode.elm = oldVnode.elm
  // ...
  const oldCh = oldVnode.children
  const ch = vnode.children
  // ...
  if (isUndef(vnode.text)) {
    if (isDef(oldCh) && isDef(ch)) {
    // When new and old Vnode.length All exist and are not equal to enter updateChildren
      if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
    } else if (isDef(ch)) {
      if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
      addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
    } else if (isDef(oldCh)) {
      removeVnodes(elm, oldCh, 0, oldCh.length - 1)
    } else if (isDef(oldVnode.text)) {
      nodeOps.setTextContent(elm, '')
  } else if (oldVnode.text !== vnode.text) {
    nodeOps.setTextContent(elm, vnode.text)
  // ...
Copy code


insertedVnodeQueue is an array queue maintained. After diff is completed, the data in the queue will be updated one by one

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    // Double pointer
    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
    // First = > last = > first = > last = > traverse old and find index instead
    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)) {
        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)
        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)
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
      } else {
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key]
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
        if (isUndef(idxInOld)) { // New element
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
        } else {
          vnodeToMove = oldCh[idxInOld]
          if (sameVnode(vnodeToMove, newStartVnode)) {
            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
            oldCh[idxInOld] = undefined
            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
          } else {
            // same key but different element. treat as new element
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
        newStartVnode = newCh[++newStartIdx]
    // Jumping out of the while loop means that the front and back pointers are interlaced
    // If the pointer of the old node is interleaved first, it means that a new node is added = > addvnodes
    // Otherwise = > removevnodes
    if (oldStartIdx > oldEndIdx) {
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else if (newStartIdx > newEndIdx) {
      removeVnodes(oldCh, oldStartIdx, oldEndIdx)
Copy code


Back to the example, in order to get more information, change the demo.

  <div id="demo">
    <p v-for="item in items" :key="item">{{ item }}</p>
    ["a", "b", "c", "d", "e", "f", "g"] => ["f", "d", "a", "h", "e", "c", "b", "g"]
  <script src="../vue/dist/vue.js"></script>
    const app = new Vue({
      el: "#demo",
      data: {
        items: ["a", "b", "c", "d", "e", "f", "g"]
      mounted() {
        setTimeout(() => {
          this.items = ["f", "d", "a", "h", "e", "c", "b", "g"]
        }, 2000)
Copy code

stay vue.js updateChildren in for observation.



First while

Tail gg match successful


newStartVnode is g, enter patchVnode. I thought I would judge if( oldVnode.text !== vnode.text )Then I didn't deal with it, but I went back to updateChildren.
Can't help but let me console.log("oldStartIdx", oldStartIdx, oldCh[oldStartIdx])



Where the structure is


I thought that VNode.text = a; VNode.children = undefined ...
A in this < p > a < / P > is also a VNode



Fortunately, the error has been corrected, and the pure text VNode will be ignored.


I'm not the only one who knows now

Then add a condition in debugger to observe oldStartVnode.tag === 'p'






Second while

The tail head matching is successful. The reference node is a node nodeOps.insertBefore

abcdefg => fabcdeg

oldStartVnode = oldCh[++oldStartIdx]; newEndVnode = newCh[--newEndIdx];


The third while

The first and last four matches are not matched. Enter the code block.

  1. Create key to oldidx (oldch, oldstartidx, oldendidx) for the remaining oldVnode (see the figure below)
  2. If idxInOld does not exist, it means new element = > createElm
  3. If idxInOld exists = > oldCh[idxInOld] = undefined = > reference node executes for a node nodeOps.insertBefore
  4. At this time, the interface also starts from fabcdeg = > fdabceg





The fourth while

Successful comparison of the first aa



The fifth while

The comparison between the first and the last bb is successful. The reference node is g node nodeOps.insertBefore

fdabceg => fdacebg



Sixth while

The comparison of the first and last cc is successful. The reference node is b node nodeOps.insertBefore

fdacebg => fdaecbg



The seventh while

At this time, oldStartVnode === undefined = > oldStartVnode = oldCh[++oldStartIdx];



Eighth while

Tail ee matching succeeded



At this time, oldendidx < oldstartidx jumps out of while and enters the following code block.

Oldendidx < oldstartidx = > added node = > reference node executes addvnodes() for e node (addvnodes execution finally also nodeOps.insertBefore ), insert the remaining nodes (H) of newCh in front of the E (refElm) node.



Press F8 once again in debugger to complete the diff process. Now the final fdahecbg is displayed

Reference link


It is strongly recommended to use Chrome browser for debugging. The coordination diagram is not easy to understand

  1. The diff key function updateChildren of rendering list
  2. Note VNode structure = > < p > a < p > = > VNode{tag: 'p', children: VNode{tag: undefined, children: undefined, text: 'a'}, text: undefined}
  3. Each cycle: first = > last = > first = > last = > FindIndex = > createelm (! Idxinold)| nodeOps.insertBefore (idxInOld)
  4. Out of loop = > addvnodes (oldstartidx > oldendidx) | removevnodes (newstartidx > newendidx)

Note: if you want to learn to practice together with the actual combat, you can get the latest enterprise class Vue3.0/Js/ES6/TS/React/node and other real combat video tutorials in 2020. You can get them for free in skirt 519293536 if you want to learn, and you can't get them in white!

The text and pictures of this article come from the Internet and my own ideas. They are only for learning and communication. They have no commercial use. The copyright belongs to the original author. If you have any questions, please contact us in time for handling

Posted by feri_soft on Mon, 08 Jun 2020 19:53:08 -0700