15 pictures, 20 minutes to understand the core principles of diff algorithm, Jesus can’t stop it! I said!!!

Time:2021-12-25

author

Sunshine_Lin

preface

In the daily interview,Diff algorithmIt’s a barrier that can’t be bypassed,In the most popular words, talk about the most difficult knowledge pointsIt has always been the purpose of my article. Today I will explain it in a popular wayDiff algorithmRight? Lets Go

15 pictures, 20 minutes to understand the core principles of diff algorithm, Jesus can't stop it! I said!!!

What is virtual DOM

speakDiff algorithmBefore, I’ll tell you what it isVirtual DOMAll right. This is good for everyone behindDiff algorithmDeepen our understanding.

Virtual DOMIt’s aobject, what kind of object?An object that represents the real dom, remember this sentence. Let me give you an example. Look at the followingReal DOM

<ul>
    <li>Ha ha</li>
    <li>Ha ha</li>
    <li>Hey, hey</li>
</ul>

CorrespondingVirtual DOMIs:

Let oldvdom = {// old virtual DOM
        TagName: 'UL', // tag name
        Props: {// tag attribute
            id: 'list'
        },
        Children: [// sign child nodes
            {
                TagName: 'Li', props: {class: 'item'}, children: ['ha ha']
            },
            {
                TagName: 'Li', props: {class: 'item'}, children: ['ha ha']
            },
            {
                TagName: 'Li', props: {class: 'item'}, children: ['hey hey']
            },
        ]
    }

At this time, I modify oneLi tagText for:

<ul>
    <li>Ha ha</li>
    <li>Ha ha</li>
    <li>Lin Sanxin hahaha < / Li > // modified
</ul>

Generated at this timeNew virtual DOMIs:

Let newvdom = {// new virtual DOM
        TagName: 'UL', // tag name
        Props: {// tag attribute
            id: 'list'
        },
        Children: [// sign child nodes
            {
                TagName: 'Li', props: {class: 'item'}, children: ['ha ha']
            },
            {
                TagName: 'Li', props: {class: 'item'}, children: ['ha ha']
            },
            {
                TagName: 'Li', props: {class: 'item'}, children: ['Lin Sanxin ha ha ha ha ha']
            },
        ]
    }

That’s what we usually sayNew and old virtual DOMS, this timeNew virtual DOMIs the latest status of the data, so let’s take it directlyNew virtual DOMTo render intoReal DOMIs it really more efficient than directly operating a real DOM? It certainly won’t. look at the figure below:

15 pictures, 20 minutes to understand the core principles of diff algorithm, Jesus can't stop it! I said!!!

From the above figure, we can see that the second method must be faster, because there is one in the middle of the first methodVirtual DOMSteps, soVirtual DOM is faster than real domThis sentence is actually wrong, or not rigorous. What is the correct statement?The virtual DOM algorithm operates the real DOM, and its performance is higher than that of directly operating the real domVirtual DOMandVirtual DOM algorithmThere are two concepts.Virtual DOM ALGORITHM = Virtual DOM + diff algorithm

What is diff algorithm

We said aboveVirtual DOMOnly, I knowVirtual DOM + diff algorithmTo really improve performance, that’s allVirtual DOM, let’s talk about it againDiff algorithmLet’s go back to the above example (the compressed image is a little small. You can open it and see it clearly):

15 pictures, 20 minutes to understand the core principles of diff algorithm, Jesus can't stop it! I said!!!

In the figure above, in fact, only one Li tag modifies the text, and the others remain unchanged. Therefore, it is not necessary to update all nodes. Just update this Li tag. Diff algorithm is the algorithm to find this Li tag.

Summary:Diff algorithm is a comparison algorithm。 Comparing the two isOld virtual Dom and new virtual DOM, compare whichVirtual nodeChanged. Find thisVirtual node, and only the corresponding virtual node is updatedReal nodeInstead of updating other nodes whose data has not changed, theaccurateUpdate the real DOM, and thenincrease of efficiency

Loss calculation using virtual DOM algorithm: total loss = Virtual DOM addition, deletion and modification + (related to diff algorithm efficiency) real DOM difference addition, deletion and modification + (fewer nodes) typesetting and redrawing

Loss calculation of direct operation of real DOM: total loss = complete addition, deletion and modification of real DOM + (possibly more nodes) typesetting and redrawing

Principle of diff algorithm

Diff same layer comparison

When comparing new and old virtual DOMS, diff algorithm will only be compared at the same level, not cross level. So the diff algorithm is:breadth first algorithm 。 Time complexity:O(n)

15 pictures, 20 minutes to understand the core principles of diff algorithm, Jesus can't stop it! I said!!!

Diff comparison process

Triggered when the data changessetter, and throughDep.notifyGo inform everyoneSubscriber watcher, subscribers will callPatch method, patch the real Dom and update the corresponding view. For those who don’t know much about this step, you can take a look at what I wrote beforeVue source code series

Newvnode and oldvnode: old and new virtual nodes in the same layer

15 pictures, 20 minutes to understand the core principles of diff algorithm, Jesus can't stop it! I said!!!

Patch method

This method is used to compare whether the current virtual nodes of the same layer are of the same type(standards of the same type will be described below)

  • Yes: continuePatchvnode methodDeep comparison
  • No: there is no need to compare. Replace the whole node withNew virtual node

Let’s seepatchCore principle code

function patch(oldVnode, newVnode) {
  //Compare whether it is a type of node
  if (sameVnode(oldVnode, newVnode)) {
    //Yes: continue to conduct in-depth comparison
    patchVnode(oldVnode, newVnode)
  } else {
    //No
    const oldEl = oldVnode. El // the real DOM node of the old virtual node
    const parentEle = api. Parentnode (oldel) // get parent node
    CreateElement (newvnode) // create the real DOM node corresponding to the new virtual node
    if (parentEle !== null) {
      api. InsertBefore (parentele, vnode.el, API. Nextsibling (OEL)) // add a new element to the parent element
      api. Removechild (parentele, oldvnode. EL) // remove the previous old element node
      //Set null to free memory
      oldVnode = null
    }
  }

  return newVnode
}

Samevnode method

The key step of patch isThe samevnode method determines whether it is a node of the same type, the question is, how can nodes of the same type be considered? thistypeWhat are the criteria for?

Let’s take a look at the core principle code of the samevnode method

function sameVnode(oldVnode, newVnode) {
  return (
    oldVnode. key === newVnode. Key & & // is the key value the same
    oldVnode. tagName === newVnode. TagName & // is the tag name the same
    oldVnode. isComment === newVnode. Iscomment & // are all comment nodes
    Isdef (oldvnode. Data) = = = isdef (newvnode. Data) & // is data defined
    Sameinputtype (oldvnode, newvnode) // when the tag is input, must the type be the same
  )
}

Patchvnode method

This function does the following:

  • Find the correspondingReal DOM, calledel
  • judgenewVnodeandoldVnodeWhether to point to the same object. If so, directlyreturn
  • If they all have text nodes and are not equal, thenelThe text node of is set tonewVnodeText node for.
  • IfoldVnodeThere are child nodes andnewVnodeIf not, deleteelChild nodes of
  • IfoldVnodeWithout child nodesnewVnodeIf yes, it willnewVnodeAfter the child nodes of are materialized, they are added to theel
  • If both have child nodes, executeupdateChildrenFunction to compare child nodes, which is very important
function patchVnode(oldVnode, newVnode) {
  const el = newVnode. el = oldVnode. El // get the real DOM object
  //Gets the child node array of the old and new virtual nodes
  const oldCh = oldVnode.children, newCh = newVnode.children
  //Terminate if the old and new virtual nodes are the same object
  if (oldVnode === newVnode) return
  //If the old and new virtual nodes are text nodes and the text is different
  if (oldVnode.text !== null && newVnode.text !== null && oldVnode.text !== newVnode.text) {
    //The text in the real DOM is directly updated to the text of the new virtual node
    api.setTextContent(el, newVnode.text)
  } else {
    //Otherwise

    if (oldCh && newCh && oldCh !== newCh) {
      //Both old and new virtual nodes have child nodes, and the child nodes are different

      //Compare child nodes and update
      updateChildren(el, oldCh, newCh)
    } else if (newCh) {
      //The new virtual node has child nodes, but the old virtual node does not

      //Create a child node of the new virtual node and update it to the real dom
      createEle(newVnode)
    } else if (oldCh) {
      //The old virtual node has child nodes, but the new virtual node does not

      //Directly delete the corresponding child nodes in the real dom
      api.removeChild(el)
    }
  }
}

The other points are well understood. Let’s talk about them in detailupdateChildren

Updatechildren method

This ispatchVnodeOne of the most important methods in the is to compare the child nodes of the old and new virtual nodesUpdatechildren methodNext, let’s combine some pictures to make you better understand

What is a comparison method? namelyHead and tail pointer method, the new child node set and the old child node set each have two pointers, for example:

<ul>
    <li>a</li>
    <li>b</li>
    <li>c</li>
</ul>

After modifying data

<ul>
    <li>b</li>
    <li>c</li>
    <li>e</li>
    <li>a</li>
</ul>

Then the new and old child node sets and their head and tail pointers are:

15 pictures, 20 minutes to understand the core principles of diff algorithm, Jesus can't stop it! I said!!!

Then they will compare with each other. There are five comparison situations:

  • 1、Olds and newsuseSamevnode methodFor comparison,sameVnode(oldS, newS)
  • 2、Olds and neweuseSamevnode methodFor comparison,sameVnode(oldS, newE)
  • 3、Olde and newsuseSamevnode methodFor comparison,sameVnode(oldE, newS)
  • 4、Olde and neweuseSamevnode methodFor comparison,sameVnode(oldE, newE)
  • 5. If none of the above logics can match, replace all the old child nodeskeyMake a mapping to the subscript of the old nodekey -> indexTable, and then use the newvnodeofkeyTo find the location that can be reused in the old node.
15 pictures, 20 minutes to understand the core principles of diff algorithm, Jesus can't stop it! I said!!!

Next, take the above code as an example to analyze the comparison process

Before analysis, please remember that the final rendering result should be subject to newvdom, which also explains why subsequent node movements need to be moved to the corresponding position of newvdom

15 pictures, 20 minutes to understand the core principles of diff algorithm, Jesus can't stop it! I said!!!

  • First step
oldS = a, oldE = c
newS = b, newE = a

Comparison results:Olds and neweEqual, need toNode aMove tonewEThe corresponding position, that is, the end, at the same timeoldS++newE--

15 pictures, 20 minutes to understand the core principles of diff algorithm, Jesus can't stop it! I said!!!

  • Step 2
oldS = b, oldE = c
newS = b, newE = e

Comparison results:Olds and newsEqual, need toNode BMove tonewSCorresponding position, andoldS++,newS++

15 pictures, 20 minutes to understand the core principles of diff algorithm, Jesus can't stop it! I said!!!

  • Step 3
oldS = c, oldE = c
newS = c, newE = e

Comparison results:Olds, olde and newsEqual, need toNode CMove tonewSCorresponding position, andoldS++,oldE--,newS++

15 pictures, 20 minutes to understand the core principles of diff algorithm, Jesus can't stop it! I said!!!

  • Step 4

oldS > oldE, thenoldChThe traversal is complete first, andnewChI haven’t finished traversing. It meansThere are more newch than oldchTherefore, you need to insert the extra nodes into the corresponding positions on the real dom

15 pictures, 20 minutes to understand the core principles of diff algorithm, Jesus can't stop it! I said!!!

  • Thinking questions

I’ll leave you a thinking question here. The example above isThere are more newch than oldch, if the opposite, yesMore oldch than newchIf so, that’snewChGo through the cycle first, and thenoldChThere will be more nodes. As a result, these old nodes will be deleted in the real dom. You can think for yourself and simulate this process. Like me, drawing and simulation can consolidate the above knowledge.

enclosedupdateChildrenCore principle code

function updateChildren(parentElm, oldCh, newCh) {
  let oldStartIdx = 0, 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
  let idxInOld
  let elmToMove
  let before
  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (oldStartVnode == null) {
      oldStartVnode = oldCh[++oldStartIdx]
    } else if (oldEndVnode == null) {
      oldEndVnode = oldCh[--oldEndIdx]
    } else if (newStartVnode == null) {
      newStartVnode = newCh[++newStartIdx]
    } else if (newEndVnode == null) {
      newEndVnode = newCh[--newEndIdx]
    } else if (sameVnode(oldStartVnode, newStartVnode)) {
      patchVnode(oldStartVnode, newStartVnode)
      oldStartVnode = oldCh[++oldStartIdx]
      newStartVnode = newCh[++newStartIdx]
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
      patchVnode(oldEndVnode, newEndVnode)
      oldEndVnode = oldCh[--oldEndIdx]
      newEndVnode = newCh[--newEndIdx]
    } else if (sameVnode(oldStartVnode, newEndVnode)) {
      patchVnode(oldStartVnode, newEndVnode)
      api.insertBefore(parentElm, oldStartVnode.el, api.nextSibling(oldEndVnode.el))
      oldStartVnode = oldCh[++oldStartIdx]
      newEndVnode = newCh[--newEndIdx]
    } else if (sameVnode(oldEndVnode, newStartVnode)) {
      patchVnode(oldEndVnode, newStartVnode)
      api.insertBefore(parentElm, oldEndVnode.el, oldStartVnode.el)
      oldEndVnode = oldCh[--oldEndIdx]
      newStartVnode = newCh[++newStartIdx]
    } else {
      //Comparison when using key
      if (oldKeyToIdx === undefined) {
        Oldkeytoidx = createkeytooldidx (oldch, oldstartidx, oldendidx) // generate index table with key
      }
      idxInOld = oldKeyToIdx[newStartVnode.key]
      if (!idxInOld) {
        api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)
        newStartVnode = newCh[++newStartIdx]
      }
      else {
        elmToMove = oldCh[idxInOld]
        if (elmToMove.sel !== newStartVnode.sel) {
          api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)
        } else {
          patchVnode(elmToMove, newStartVnode)
          oldCh[idxInOld] = null
          api.insertBefore(parentElm, elmToMove.el, oldStartVnode.el)
        }
        newStartVnode = newCh[++newStartIdx]
      }
    }
  }
  if (oldStartIdx > oldEndIdx) {
    before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].el
    addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx)
  } else if (newStartIdx > newEndIdx) {
    removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
  }
}

Use index as key

In normal V-for loop rendering, why not recommend using index as the key of the loop item?

Let’s take an example. The initial data is on the left, and then I insert a new data in front of the data to become a list on the right

<ul>                      <ul>
    < Li key = "0" > a < / Li > < Li key = "0" > Lin Sanxin</li>
    <li key="1">b</li>        <li key="1">a</li>
    <li key="2">c</li>        <li key="2">b</li>
                              <li key="3">c</li>
</ul>                     </ul>

Logically speaking, the most ideal result is to insert only one Li tag new node and leave the others unchanged to ensure the highest efficiency of DOM operation. But if we use index as the key here, will we really achieve our ideal result? Without much nonsense, practice it:

<ul>
   <li v-for="(item, index) in list" :key="index">{{ item.title }}</li>
</ul>
< button @ Click = "add" > Add < / button >

list: [
        { title: "a", id: "100" },
        { title: "b", id: "101" },
        { title: "c", id: "102" },
      ]

add() {
      this. list. Unshift ({Title: "Lin Sanxin", ID: "99"});
    }

Click the button and we can see that not the result we expected, but all Li tags have been updated

15 pictures, 20 minutes to understand the core principles of diff algorithm, Jesus can't stop it! I said!!!

Why? Or through the diagram to explain

As a matter of course,a,b,cThe three li tags are all before reuse, because the three of them have not changed at all. What has changed is that a new one has been added in frontLin Sanxin

15 pictures, 20 minutes to understand the core principles of diff algorithm, Jesus can't stop it! I said!!!

But as we said earlier, we are in the process of child nodeDiff algorithmDuring the process, the old first node and the new first node will besameNodeIn contrast, this step hits the logic, because nowNew and old two header nodesofkeyAll0Similarly, those with keys 1 and 2 also hit the logic, resulting inNodes with the same keyI’ll do itpatchVnodeUpdate the text, which is already thereC nodeHowever, because there was no node with key 4 before, it was regarded as a new node, so it was funny. Using index as the key, the last new node was the existing C node. So the first three are carried outpatchVnodeUpdate the text, and the last one is updatednewly added, that explains why all Li tags have been updated.

15 pictures, 20 minutes to understand the core principles of diff algorithm, Jesus can't stop it! I said!!!

How can we solve it? In fact, we just need to use a unique value as the key

<ul>
   <li v-for="item in list" :key="item.id">{{ item.title }}</li>
</ul>

Now let’s see the effect

15 pictures, 20 minutes to understand the core principles of diff algorithm, Jesus can't stop it! I said!!!

Why do we use ID as key to achieve our ideal effect? Because if we do so,a. B, C nodesofkeyThe key will always be the same before and after the update, and becausea. B, C nodesThe content of has not changed, so it’s even going onpatchVnode, it will not perform complex update operations inside, which saves performance. However, Lin Sanxin node is treated as a new node and added to the real DOM because there is no node corresponding to its key before updating.

15 pictures, 20 minutes to understand the core principles of diff algorithm, Jesus can't stop it! I said!!!

epilogue

I hope it can help those students who have always wanted to understand virtual Dom and diff algorithm

If you think this article can help you a little, please give me a praise, ha ha

15 pictures, 20 minutes to understand the core principles of diff algorithm, Jesus can't stop it! I said!!!

Recommended Today

Example of realizing canvas shadow effect with HTML5

Realize canvas shadow effect in HTML5 Copy code The code is as follows: <!DOCTYPE html><html><head><meta http-equiv=”X-UA-Compatible” content=”chrome=IE8″><meta http-equiv=”Content-type” content=”text/html;charset=UTF-8″><title>Canvas Clip Demo</title><link href=”default.css” rel=”stylesheet” /> <script> var ctx = null; // global variable 2d context var imageTexture = null; window.onload = function() { var canvas = document.getElementById(“text_canvas”); console.log(canvas.parentNode.clientWidth); canvas.width = canvas.parentNode.clientWidth; canvas.height = canvas.parentNode.clientHeight; if (!canvas.getContext) […]