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

Time：2021-12-25

Sunshine_Lin

## preface

In the daily interview,`Diff algorithm`It’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 way`Diff algorithm`Right? Lets Go

## What is virtual DOM

speak`Diff algorithm`Before, I’ll tell you what it is`Virtual DOM`All right. This is good for everyone behind`Diff algorithm`Deepen our understanding.

`Virtual DOM`It’s a`object`, what kind of object?An object that represents the real dom, remember this sentence. Let me give you an example. Look at the following`Real DOM`

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

Corresponding`Virtual DOM`Is:

``````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 one`Li tag`Text for:

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

Generated at this time`New virtual DOM`Is:

``````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 say`New and old virtual DOMS`, this time`New virtual DOM`Is the latest status of the data, so let’s take it directly`New virtual DOM`To render into`Real DOM`Is it really more efficient than directly operating a real DOM? It certainly won’t. look at the figure below:

From the above figure, we can see that the second method must be faster, because there is one in the middle of the first method`Virtual DOM`Steps, 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 dom`Virtual DOM`and`Virtual DOM algorithm`There are two concepts.`Virtual DOM ALGORITHM = Virtual DOM + diff algorithm`

## What is diff algorithm

We said above`Virtual DOM`Only, I know`Virtual DOM + diff algorithm`To really improve performance, that’s all`Virtual DOM`, let’s talk about it again`Diff algorithm`Let’s go back to the above example (the compressed image is a little small. You can open it and see it clearly):

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 is`Old virtual Dom and new virtual DOM`, compare which`Virtual node`Changed. Find this`Virtual node`, and only the corresponding virtual node is updated`Real node`Instead of updating other nodes whose data has not changed, the`accurate`Update the real DOM, and then`increase 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)`

### Diff comparison process

Triggered when the data changes`setter`, and through`Dep.notify`Go inform everyone`Subscriber watcher`, subscribers will call`Patch 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

### 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: continue`Patchvnode method`Deep comparison
• No: there is no need to compare. Replace the whole node with`New virtual node`

Let’s see`patch`Core 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 is`The samevnode method determines whether it is a node of the same type`, the question is, how can nodes of the same type be considered? this`type`What 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 corresponding`Real DOM`, called`el`
• judge`newVnode`and`oldVnode`Whether to point to the same object. If so, directly`return`
• If they all have text nodes and are not equal, then`el`The text node of is set to`newVnode`Text node for.
• If`oldVnode`There are child nodes and`newVnode`If not, delete`el`Child nodes of
• If`oldVnode`Without child nodes`newVnode`If yes, it will`newVnode`After the child nodes of are materialized, they are added to the`el`
• If both have child nodes, execute`updateChildren`Function 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 detail`updateChildren`

### Updatechildren method

This is`patchVnode`One of the most important methods in the is to compare the child nodes of the old and new virtual nodes`Updatechildren method`Next, let’s combine some pictures to make you better understand

What is a comparison method? namely`Head 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:

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

• 1、`Olds and news`use`Samevnode method`For comparison,`sameVnode(oldS, newS)`
• 2、`Olds and newe`use`Samevnode method`For comparison,`sameVnode(oldS, newE)`
• 3、`Olde and news`use`Samevnode method`For comparison,`sameVnode(oldE, newS)`
• 4、`Olde and newe`use`Samevnode method`For comparison,`sameVnode(oldE, newE)`
• 5. If none of the above logics can match, replace all the old child nodes`key`Make a mapping to the subscript of the old node`key -> index`Table, and then use the new`vnode`of`key`To find the location that can be reused in the old node.

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

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

Comparison results:`Olds and newe`Equal, need to`Node a`Move to`newE`The corresponding position, that is, the end, at the same time`oldS++``newE--`

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

Comparison results:`Olds and news`Equal, need to`Node B`Move to`newS`Corresponding position, and`oldS++`,`newS++`

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

Comparison results:`Olds, olde and news`Equal, need to`Node C`Move to`newS`Corresponding position, and`oldS++`,`oldE--`,`newS++`

• Step 4

`oldS > oldE`, then`oldCh`The traversal is complete first, and`newCh`I haven’t finished traversing. It means`There are more newch than oldch`Therefore, you need to insert the extra nodes into the corresponding positions on the real dom

• Thinking questions

I’ll leave you a thinking question here. The example above is`There are more newch than oldch`, if the opposite, yes`More oldch than newch`If so, that’s`newCh`Go through the cycle first, and then`oldCh`There 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.

enclosed`updateChildren`Core 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
} 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" },
]

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

Why? Or through the diagram to explain

As a matter of course,`a，b，c`The 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 front`Lin Sanxin`

But as we said earlier, we are in the process of child node`Diff algorithm`During the process, the old first node and the new first node will be`sameNode`In contrast, this step hits the logic, because now`New and old two header nodes`of`key`All`0`Similarly, those with keys 1 and 2 also hit the logic, resulting in`Nodes with the same key`I’ll do it`patchVnode`Update the text, which is already there`C node`However, 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 out`patchVnode`Update the text, and the last one is updated`newly added`, that explains why all Li tags have been updated.

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

Why do we use ID as key to achieve our ideal effect? Because if we do so,`a. B, C nodes`of`key`The key will always be the same before and after the update, and because`a. B, C nodes`The content of has not changed, so it’s even going on`patchVnode`, 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.

## epilogue

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