author
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 wayDiff algorithm
Right? Lets Go

What is virtual DOM
speakDiff algorithm
Before, I’ll tell you what it isVirtual DOM
All right. This is good for everyone behindDiff algorithm
Deepen our understanding.
Virtual DOM
It’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 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 oneLi tag
Text for:
<ul>
<li>Ha ha</li>
<li>Ha ha</li>
<li>Lin Sanxin hahaha < / Li > // modified
</ul>
Generated at this timeNew 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 sayNew and old virtual DOMS
, this timeNew virtual DOM
Is the latest status of the data, so let’s take it directlyNew virtual DOM
To render intoReal 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 methodVirtual 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
andVirtual DOM algorithm
There are two concepts.Virtual DOM ALGORITHM = Virtual DOM + diff algorithm
What is diff algorithm
We said aboveVirtual DOM
Only, I knowVirtual DOM + diff algorithm
To really improve performance, that’s allVirtual DOM
, let’s talk about it againDiff 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 isOld virtual Dom and new virtual DOM
, compare whichVirtual node
Changed. Find thisVirtual node
, and only the corresponding virtual node is updatedReal node
Instead of updating other nodes whose data has not changed, theaccurate
Update 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)

Diff comparison process
Triggered when the data changessetter
, and throughDep.notify
Go 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

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 seepatch
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 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? thistype
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
, calledel
- judge
newVnode
andoldVnode
Whether to point to the same object. If so, directlyreturn
- If they all have text nodes and are not equal, then
el
The text node of is set tonewVnode
Text node for. - If
oldVnode
There are child nodes andnewVnode
If not, deleteel
Child nodes of - If
oldVnode
Without child nodesnewVnode
If yes, it willnewVnode
After the child nodes of are materialized, they are added to theel
- 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 detailupdateChildren
Updatechildren method
This ispatchVnode
One of the most important methods in the is to compare the child nodes of the old and new virtual nodesUpdatechildren method
Next, 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:

Then they will compare with each other. There are five comparison situations:
- 1、
Olds and news
useSamevnode method
For comparison,sameVnode(oldS, newS)
- 2、
Olds and newe
useSamevnode method
For comparison,sameVnode(oldS, newE)
- 3、
Olde and news
useSamevnode method
For comparison,sameVnode(oldE, newS)
- 4、
Olde and newe
useSamevnode 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 nodekey -> index
Table, and then use the newvnode
ofkey
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 toNode a
Move tonewE
The corresponding position, that is, the end, at the same timeoldS++
,newE--

- Step 2
oldS = b, oldE = c
newS = b, newE = e
Comparison results:Olds and news
Equal, need toNode B
Move tonewS
Corresponding position, andoldS++
,newS++

- Step 3
oldS = c, oldE = c
newS = c, newE = e
Comparison results:Olds, olde and news
Equal, need toNode C
Move tonewS
Corresponding position, andoldS++
,oldE--
,newS++

- Step 4
oldS > oldE
, thenoldCh
The traversal is complete first, andnewCh
I haven’t finished traversing. It meansThere 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 isThere are more newch than oldch
, if the opposite, yesMore oldch than newch
If so, that’snewCh
Go through the cycle first, and thenoldCh
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.
enclosedupdateChildren
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
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

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 frontLin Sanxin

But as we said earlier, we are in the process of child nodeDiff algorithm
During the process, the old first node and the new first node will besameNode
In contrast, this step hits the logic, because nowNew and old two header nodes
ofkey
All0
Similarly, those with keys 1 and 2 also hit the logic, resulting inNodes with the same key
I’ll do itpatchVnode
Update the text, which is already thereC 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 outpatchVnode
Update the text, and the last one is updatednewly 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
ofkey
The key will always be the same before and after the update, and becausea. B, C nodes
The 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.

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
