Brief introduction in plain English
In this section, a brief description of diff will not appear any source code, just to help you establish an idea, understand the general content of diff.
1. The role of Diff
2. Diff's approach
3. Comparative logic of Diff 4. Simple examplesCopy the code
1. The Diff
In order to reduce the number of updates, Diff will find the least different part of the DOM and update only the different part of the DOM
So it’s going to cost less
The data changes, so there is no need to replace the other unchanged DOM that is not involved
2. The Diff
Vue only compares the level of child nodes whose parent is the same between the old and new nodes
Or you could say that
Children of two old and new nodes are compared only if they are the same node
The largest root nodes can initially be compared directly
This is also called same-level comparison, and it doesn’t require recursion, although it seems to reduce reusability a little bit, but also to avoid over-optimization, and it’s a very efficient Diff algorithm
What are the old and new nodes?
All old and new nodes refer to vnodes, and Vue only compares VNodes, not DOM
Because Vnode is a JS object, it is platform-independent, so it is used as a basis for comparison and the code logic does not need to be changed later
After getting the comparison results, call the corresponding methods for processing according to different platforms
What does it mean that the parent node is the same node?
For example, the four comparisons shown in the figure below (from first to Fouth) have the same parent node in common
For example, in the blue square, the parent of the old and new child nodes is the same node 1
For example, in the red square, the parent of both the old and new children is 2
So they have a chance to compare
In the figure below, there are only two comparisons, because in the blue square comparison, there is no same node, so there is no comparison of the lower child node
3. Diff is logical
The core of Diff comparison is node reuse, so the purpose of Diff comparison is to find the same node between the old and new nodes
The logic of this comparison is based on the same-level comparison mentioned in the previous step
So, node reuse, finding the same node is not an unlimited recursive search
For example, in the following figure, it is true that the old node tree and the new node tree have the same node 6, but the old node 6 is not reused. Right
Even if it’s on the same level, but the parent node is different, it still doesn’t help, right
Only in this case will the node be reused, the same parent node 8
Here’s Diff’s comparative logic
1, can not move, try not to move
There is no way, but to move
3. If it is impossible, create or delete it
The comparison process looks like this
In the old and new nodes
1. Find the same node that does not need to be moved first, with minimum consumption
2. Find the same node that needs to be moved again, which consumes the second smallest amount
3, finally can not find, will go to the new deletion node, guarantee the bottom processing
The comparison is to modify the DOM tree
In fact, there are three kinds of trees, one is the page DOM tree, one is the old VNode tree, one is the new VNode tree
Page DOM tree and the old VNode tree node one-to-one correspondence
The new Vnode tree represents what the updated PAGE DOM tree should look like
Here is the process of comparing the old Vnode tree with the new Vnode tree
The two Vode trees are not modified, but are modified directly to the real DOM as a result of the comparison
For example, in the same layer of the old Vnode tree, find the same node as in the new Vnode tree, but in a different position
You need to move the node, but not the node in the old Vnode tree
Instead, move the DOM directly
In general, the old and new Vnode trees are compared, and the page DOM tree is modified based on the comparison results
If you’re confused, let’s just give you a quick example
4. Diff simple example
For example, here are two old and new node trees that need to be compared and a page DOM tree that needs to be modified
The first round of comparison begins
Since the parent nodes are all 1, we start comparing their children
According to our comparison logic above, so first find the same && points that do not need to be moved
Sure enough, find 2
To get the comparison results, we don’t have to change the DOM here, so the DOM stays where it is
The second round of comparison begins
Then, there are no identical nodes that need not be moved
The second option is to start looking for the same point
Find node 5, same but different location, so need to move
Get the comparison result, the page DOM tree needs to move the DOM, do not modify, unmodified move
The third round of comparison begins
Continue, ho ho, the same node is also lost, there is no other way to create
Create and insert nodes that are not found in the new Vnode
However, some nodes in the old Vnode do not exist in the new Vnode, so they need to be deleted
Start creating nodes 6 and 9, and delete nodes 3 and 4
The page is then updated
How, have interest, interested in the source code ha ha
2. Start diff from creating an instance
This section will not delve into the source code, but rather walk through the process with examples.
This section is short
Get a handle on the process, then explore Diff’s ideas in detail
First, when you create a new instance, like this
You call a Vue function, so take a look at the Vue function
function Vue() {
. Others have been omitted
new Watcher(function() {
vm._update(vm._render()); }) . Others have been omitted } Copy the code
The function does two things
1. Create a new Watcher for your instance
2. Update the callback for the watcher binding.
Each instance will have a dedicated Watcher, and the bound callback will be called when the page is updated
Let’s take a look at the simplified Watcher source code
funciton Watcher(expOrFn){
this.getter = expOrFn;
this.get();
} Watcher.prototype.get = function () { this.getter() } Copy the code
Watcher saves the update callback and invokes it immediately when a new Watcher is created
Now let’s move on to the update callback
vm._update(vm._render());
Copy the code
If you read the previous article you should know what these two functions do
So let’s talk about it briefly
vm._render
Generate a Vnode tree for the page template, for example
The generated Vnode tree is (where num is 111)
{
tag: "div".
children: [{ tag: "span" }, { tag: undefined. text: "111" }] } Copy the code
This step is generated by compile, specific words can simply look at learning Vue source code (6) familiar with template compilation principle
vm._update
Compare the old Vnode tree with the new Vnode tree generated by vm._render
After the comparison, update the DOM of the page to complete the update
Ok, let’s take a look at the source code
Vue.prototype._update = function(vnode) {
var vm = this;
var prevEl = vm.$el;
var prevVnode = vm._vnode; vm._vnode = vnode; // There is no old node if(! prevVnode) { vm.$el = vm.__patch__( vm.$el, vnode, vm.$options._parentElm, vm.$options._refElm ); } else { vm.$el = vm.__patch__( prevVnode, vnode ); } }; Copy the code
Explain some of the points
1 vm._vnode
This property holds the current Vnode tree
When the page is updated and a new Vnode tree is generated
This property will be replaced with the new Vnode
So I’m saving it here so I can get my old Vnode tree
2 vm.patch
Yes, that’s right. You saw this in two places
This is the main thing in the Diff, it’s a lot of stuff, it’s a lot of stuff, I won’t talk about it here, I’m just exploring the flow today
But where did this thing come from
var patch = createPatchFunction();
Vue.prototype.__patch__ = patch ;
Copy the code
Well, it was created by a createPatchFunciton
Then assign the value to the prototype of Vue, so you can call vm.patch
Let’s talk about the two patches in the _update function
(1) There is no old node
Create all of them without comparing them
Vm.$el stores DOM nodes, and if there are no old nodes, then vm.$el does not exist at this time
If vm.$el is null, Patch will directly create the DOM without performing other operations
(2) Old nodes exist
We need to compare the old node with the new node, try to find the least difference part, and then update it. This part is the focus of Diff, which requires a lot of energy.
Okay, that’s the end of this video.
3. Related auxiliary functions
Before we begin our main Diff, let’s look at some of the helper functions that will be used
I could have put it in the Diff, but it all adds up to too much
And these functions are more common, just pull them out and use them
So plan an independent section, first warm up, the content is not much, also very simple, light will also help our thinking
1. Node operation functions
Here are some of the functions that Diff uses to update the DOM when comparing nodes
There would have been more, but I’ve combined some of them to make it easier to see
Three functions are described below
Insert, createElm, createChildren
A simple introduction
Insert is used to insert a node into a location
CreateElm creates a DOM node
CreateChildren also creates DOM nodes, but handles an array and creates DOM nodes and text nodes
Let’s take a closer look at these three methods
insert
This function is used to insert nodes
But there are two cases of insertion
1. Insert the end of the child node of the parent node directly without reference to sibling nodes
2. If there is a reference sibling node, insert it in front of the sibling node
This function is the cornerstone of Diff
function insert(parent, elm, ref) {
if (parent) {
if (ref) {
if (ref.parentNode === parent) { parent.insertBefore(elm, ref); } } else { parent.appendChild(elm); } } } Copy the code
createElm
This function, as its name suggests, creates a node
After the node is created, insert is called to insert the node
You can have a look. It’s not hard
function createElm(vnode, parentElm, refElm) {
var children = vnode.children;
var tag = vnode.tag;
if (tag) { vnode.elm = document.createElement(tag) // Insert the child node into vnode.elm and then vnode.elm into the parent node createChildren(vnode, children); // Insert DOM node insert(parentElm, vnode.elm, refElm); } else { vnode.elm = document.createTextNode(vnode.text); insert(parentElm, vnode.elm, refElm); } } Copy the code
CreateElm requires Vnode to determine which node to create
1. Text nodes
When vNode. tag is undefined, create a text node and look at the actual text vnode
Also, text nodes have no children
2. Common nodes
If the vnode.tag has a value, create the corresponding DOM
But the DOM may have child nodes, so child nodes, and even descendants, are created
So a createChildren is called to complete the creation of all descendant nodes
createChildren
This method deals with the children, one by one, in a traversal recursive fashion
1 If the child nodes are arrays, execute createElm one by one
2 If the text attribute of the child node has data, it indicates that the vnode is a text node. Create a text node and insert it into the parent node
function createChildren(vnode, children) {
if (Array.isArray(children)) {
for (var i = 0; i < children.length; ++i) {
createElm(children[i], vnode.elm, null);
} } else if ( typeof vnode.text=== 'string' || typeof vnode.text=== 'number' || typeof vnode.text=== 'boolean' ) { vnode.elm.appendChild( document.createTextNode(vnode.text) ) } } Copy the code
Service Diff utility functions
The following functions are specifically used by Vue to service Diff, two of which are described
CreateKeyToOldIdx, sameVnode
createKeyToOldIdx
Receive a children array and generate a map table with key and index
function createKeyToOldIdx(
children, beginIdx, endIdx
) {
var i, key;
var map = {};
for (i = beginIdx; i <= endIdx; ++i) { key = children[i].key; if (key) { map[key] = i; } } return map } Copy the code
Let’s say your old array of child nodes is
[{
tag:"div".key: "key_1"
}, { tag:"strong".key:"key_2"
}, { tag:"span".key:"key_4" }] Copy the code
CreateKeyToOldIdx generates a map oldKeyToIdx, like this
{
"key_1": 0. "key_2": 1, "key_4": 2}
Copy the code
Use the key of the vnode as the attribute name and the location of the vnode at children as the attribute value
The function in Diff is
Determine if a new vnode is in the old vNode array and get its location. Get the key of the new Vnode, and then go to the map table to match the corresponding node, if there is, return the node location
Such as
Now I have a new child array, an old child array
I get a newVnode in the new child array, and I want to determine if it’s the same as a vnode in the old child array
How to judge?? Newvnode. key==vnode.key??
Vue takes a smarter approach, using the old Vnode array to generate a map object, obj
When obj[newvNode. key] is present, the node is present in both the old and new child node arrays
And I can get the position of the node in the old child node array (property value)
Otherwise, it does not exist
This approach also gives us a solution to a similar scenario in a project, replacing array traversal with object index lookup
I hope you keep that in mind
sameVnode
This function also plays a very important role in the Diff, so keep that in mind
Its purpose is to determine whether two nodes are the same
Here said the same, not exactly the same, but the same key attributes, you can first look at the source code
function sameVnode(a, b) {
return (
a.key === b.key &&
a.tag === b.tag &&
!!!!! a.data === !! b.data && sameInputType(a, b) ) } function sameInputType(a, b) { if(a.tag ! = ='input') return true var i; var types = [ 'text'.'number'.'password'. 'search'.'email'.'tel'.'url' ] var typeA = (i = a.data) && (i = i.attrs) && i.type; var typeB = (i = b.data) && (i = i.attrs) && i.type; // Input is of the same type, or both of the basic input type return ( typeA === typeB || types.indexOf(typeA)>- 1 && types.indexOf(typeB)>- 1 ) } Copy the code
The judgment is mainly based on three points: key, tag, and whether there is data
The nodes judged here are only relative to the nodes themselves, excluding children
In other words, even if data and children are different, the two nodes may still be the same
Like these two
One special case is the input node
Input requires an additional check on whether the type of the two nodes is the same
or
The types of the two nodes can be different, but they must belong to those input types
That’s it for sameVnode, but I can’t help but wonder
Why does sameVnode judge this?
The following is purely personal fantasy ideas, only for reference
SameVnode is used in Diff to determine whether a node needs to be created
When two old and new vNodes are sameVnode false, the two vNodes are different and a DOM is inserted
That is, two nodes are created when they are fundamentally different
There is no doubt that the unique identifier key is compared to the tag name tag to see if the VNode is the same
But there’s no need to judge whether the data is the same or not, so I didn’t get it at first
Data contains attributes in the DOM, so it doesn’t matter if data is different
Because even if they’re different, they’re still based on the same DOM
Since the value of DOM attribute may be dynamically bound and dynamically updated, the corresponding data of the two VNodes before and after the change must be different, but in fact they are the same Vnode, so data is not in the judgment category
But data must be defined in both old and new nodes, or not at all
There is no definition, and an undefined Vnode will be the same
For example, this one down here will have data
There’s no data in this one
They must not belong to the same node in the template
conclusion
The functions involved fall into two main categories
Insert, createElm, and createChildren are specifically responsible for manipulating the DOM
This type of function is more general and can be used even in our own projects
One class is createKeyToOldIdx, sameVnode, for special service Diff
It will include some project solutions
Keep these functions in mind, as they will appear frequently in the source code in the next section
I’m not going to talk about it
4. The Diff process
Diff isn’t much to talk about, but if it’s going to be detailed, it’s a lot to talk about, and a lot of pictures
This is the last section of the Diff, the most important and the most detailed
So there’s a lot to cover in this section, so let me give you an overview
1, analysis of Diff source comparison steps
2, personal thinking why so compared
Write an example and go through the Diff process step by step
The article is long and detailed, and I recommend reading the source code if you’re interested
Let’s begin our text
Earlier, we explored how Vue goes from creating a new instance to starting a diff
You should remember that one of the important functions Diff involves is createPatchFunciton
var patch = createPatchFunction();
Vue.prototype.__patch__ = patch
Copy the code
So let’s look at this function
createPatchFunction
function createPatchFunction() {
return function patch(
oldVnode, vnode, parentElm, refElm
) {
// Create a new node without the old node
if(! oldVnode) { createElm(vnode, parentElm, refElm); } else { // It is the same Vnode if (sameVnode(oldVnode, vnode)) { // Compare existing root nodes patchVnode(oldVnode, vnode); } else { // Replace existing elements var oldElm = oldVnode.elm; var _parentElm = oldElm.parentNode // Create a node createElm(vnode, _parentElm, oldElm.nextSibling); // Destroy the old node if (_parentElm) { removeVnodes([oldVnode], 0.0); } } } return vnode.elm } } Copy the code
And what this function does is
What is the difference between the new node and the old node, and then complete the update
So you see receiving an oldVnode and a VNode
The processing process is divided into
1. No old node
2. The old node is the same as the new node itself (excluding its children).
3. The old node is different from the new node Copy the code
Let’s speed up the three processes
1. No old node exists
There are no old nodes, indicating that the page is just starting to initialize, at this point, there is no need to compare
It’s all new, so just call createElm
2. The old node is the same as the new node
The sameVnode function is used to determine whether the nodes are the same, as described in the previous section
When the old node is the same as the new node itself, patchVnode is directly called to process the two nodes
PatchVnode is described below
Before talking about patchVnode, what is the function of this function?
What do we need to do when two VNodes are themselves the same?
The tag and key attributes of a Vnode are the same
So, we don’t know if the children are the same, so we definitely need to compare the children
Therefore, one of the functions of patchVnode is to compare child nodes
3. The old node is different from the new node
When two nodes are not the same, it is not difficult to understand, directly create a new node, delete the old node
patchVnode
In the previous function createPatchFunction, there was a function patchVnode
We consider that one of the functions of this function is to compare the children of two VNodes
Is it what we want? We can have a look at the source code first
function patchVnode(oldVnode, vnode) {
if (oldVnode === vnode) return
var elm = vnode.elm = oldVnode.elm;
var oldCh = oldVnode.children; var ch = vnode.children; / / update the children if(! vnode.text) { // oldCh and ch exist if (oldCh && ch) { if(oldCh ! == ch) updateChildren(elm, oldCh, ch); } // If newCh exists, oldCh does not exist else if (ch) { if (oldVnode.text) elm.textContent = ' '; for (var i = 0; i <= ch.length - 1; ++i) { createElm( ch[i],elm, null ); } } else if (oldCh) { for (var i = 0; i<= oldCh.length - 1; ++i) { oldCh[i].parentNode.removeChild(el); } } else if (oldVnode.text) { elm.textContent = ' '; } } else if(oldVnode.text ! == vnode.text) { elm.textContent = vnode.text; } } Copy the code
So let’s analyze this function now
Yes, as we thought, this function does compare and process child nodes
So in general, what this function does is
1, Vnode is a text node, update text (text node does not have child nodes)
2. Vnode has child nodes
A further summary is that this function does two main kinds of judgment processing
1. Vnode is a text node
2. Check whether the Vnode has child nodes
Let’s look at a detailed analysis of these steps
1 Vnode is a text node
A VNode is a text node when it has the text attribute
Let’s start by looking at what a text Vnode looks like
So when a Vnode is a text node, all you need to do is update the text
There are also two treatments
1. When a new vnode. text exists and is different from the old vnode. text
Update the text content of the DOM directly
elm.textContent = vnode.text;
Copy the code
Note: textContent is a property of the real DOM that holds the text of the DOM, so update this property directly
2, the text of the new Vnode is null, directly assign the text DOM to null
elm.textContent = ' ';
Copy the code
2 The Vnode has child nodes
When a Vnode has child nodes, it is not known whether the child nodes of the old and new nodes are the same, so it needs to be compared to complete the update
There are three treatments
1. Both old and new nodes have children, and they are different
2. Only new nodes are available
3. Only old nodes are availableCopy the code
The last two nodes, I’m sure you can figure it out, but let’s just say it
1 Only new nodes exist
There’s only new nodes, there’s no old nodes, there’s no comparison, all nodes are brand new
So just create all the new DOM, which is to create all the new DOM and add it to the parent node
2 Only old nodes exist
There are only old nodes but no new nodes, indicating that the updated page, the old nodes are missing
So all you have to do is delete all the old nodes
So you just delete the DOM
3 Both old and new nodes have children, and they are different
Gee, there’s a new function, update Dren
Notice that this function is very important. It is the core module of Diff and contains the idea of Diff
It may be a little convoluted, but don’t worry, I believe that with my exploration, I can make a little bit more sense
Again, let’s think about the role of Update Dren
Remember the condition, when the new node and the old node both exist, how do you compare them so you know what the difference is?
Oh yes, using traversal, the new child is compared to the old child
If it’s the same, don’t update it, if not, update it
Let’s test our ideas and explore the updateChildren source code
updateChildren
This function is very long, but it is not difficult, just several processes, but at first it may seem a bit confusing
Or you can skip the source code and look at the analysis, or you can look at the analysis and look at the source code
function updateChildren(parentElm, oldCh, newCh) {
var oldStartIdx = 0;
var oldEndIdx = oldCh.length - 1;
var oldStartVnode = oldCh[0]; var oldEndVnode = oldCh[oldEndIdx]; var newStartIdx = 0; var newEndIdx = newCh.length - 1; var newStartVnode = newCh[0]; var newEndVnode = newCh[newEndIdx]; var oldKeyToIdx, idxInOld, vnodeToMove, refElm; // Continuously update OldIndex and OldVnode, newIndex and newVnode while ( oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx ) { if(! oldStartVnode) { oldStartVnode = oldCh[++oldStartIdx]; } else if(! oldEndVnode) { oldEndVnode = oldCh[--oldEndIdx]; } // Compare the old head with the new head else if (sameVnode(oldStartVnode, newStartVnode)) { patchVnode(oldStartVnode, newStartVnode); oldStartVnode = oldCh[++oldStartIdx]; newStartVnode = newCh[++newStartIdx]; } // The old tail is compared with the new tail else if (sameVnode(oldEndVnode, newEndVnode)) { patchVnode(oldEndVnode, newEndVnode); oldEndVnode = oldCh[--oldEndIdx]; newEndVnode = newCh[--newEndIdx]; } // The old head is compared with the new tail else if (sameVnode(oldStartVnode, newEndVnode)) { patchVnode(oldStartVnode, newEndVnode); // Place oldStartVnode after oldEndVnode and find the node after oldEndValue parentElm.insertBefore( oldStartVnode.elm, oldEndVnode.elm.nextSibling ); oldStartVnode = oldCh[++oldStartIdx]; newEndVnode = newCh[--newEndIdx]; } // The old tail is compared with the new head else if (sameVnode(oldEndVnode, newStartVnode)) { patchVnode(oldEndVnode, newStartVnode); // Place oldEndVnode before oldStartVnode parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm); oldEndVnode = oldCh[--oldEndIdx]; newStartVnode = newCh[++newStartIdx]; } // A single new child node looks up a location in the old child node array else { // oldKeyToIdx is a map that converts Vnode keys to index if(! oldKeyToIdx) { oldKeyToIdx = createKeyToOldIdx( oldCh, oldStartIdx, oldEndIdx ); } // Use newStartVnode to find the same node in OldMap. The default key exists idxInOld = oldKeyToIdx[newStartVnode.key] // There is a new node in the new child, but there is no old node if(! idxInOld) { // Insert newStartVnode before oldStartVnode createElm( newStartVnode, parentElm, oldStartVnode.elm ); } else { // Find the same node in oldCh as newStartVnode vnodeToMove = oldCh[idxInOld]; if (sameVnode(vnodeToMove, newStartVnode)) { patchVnode(vnodeToMove, newStartVnode); // Delete the index oldCh[idxInOld] = undefined; // Move vnodeToMove in front of oldStartVnode parentElm.insertBefore( vnodeToMove.elm, oldStartVnode.elm ); } // Only one new node can be created and inserted into the children of parentElm else { // same key but different element. treat as new element createElm( newStartVnode, parentElm, oldStartVnode.elm ); } } // When the new child node is updated, update newStartIdx to start comparing the next one newStartVnode = newCh[++newStartIdx]; } } // Process the remaining nodes if (oldStartIdx > oldEndIdx) { var newEnd = newCh[newEndIdx + 1] refElm = newEnd ? newEnd.elm :null; for (; newStartIdx <= newEndIdx; ++newStartIdx) { createElm( newCh[newStartIdx], parentElm, refElm ); } } // The new nodes are compared. The old nodes may still exist. Delete the remaining old nodes else if (newStartIdx > newEndIdx) { for (; oldStartIdx<=oldEndIdx; ++oldStartIdx) { oldCh[oldStartIdx].parentNode.removeChild(el); } } } Copy the code
First of all, what does this function deal with
We’re dealing with the new and old child nodes, and we’re doing a loop over one by one
How do I loop through it?
1. Use while
2. Both the old and new node arrays are configured with the first and last indexes
Two indexes for the new node: newStartIdx and newEndIdx
Two indexes of the old node: oldStartIdx and oldEndIdx
Traversal in the form of two sides enclosing in the middle
When the children of the header are compared, startIdx is incremented by 1
When the children at the end are compared, endIdex decreases by 1
As soon as one of the arrays completes traversal (startIdx<endIdx), the traversal is complete
The source code process is divided into two parts
1. Compare the old and new child nodes
2. After the comparison, process the remaining nodesCopy the code
Let’s walk through the two processes one by one
1 Compare the old and new child nodes
Note: There are two arrays, a new child Vnode array and an old child Vnode array
During the comparison, no changes are made to the two arrays (e.g. no inserts, no subitems are deleted)
And all the comparison process is directly insert and delete the real page DOM
Let’s be clear, what’s the purpose of the comparison?
Find the same child node between the old and new child nodes, and update the DOM as much as possible by moving instead of creating
It will only be created if it’s really different
Compare the update plan steps:
First of all, don’t move the DOM
Second, move the DOM
Finally, consider creating/deleting the DOM
Try not to move if you can. Move if you can’t, build if you can’t
Let’s start with the comparison logic in the source code
The five comparative logics are as follows
1. Old head = new head
2, Old tail == new tail
Old head == new tail 4. Old tail = new head 5. Single searchCopy the code
Let’s analyze the five comparative logics
1 Old head == new head
sameVnode(oldStartVnode, newStartVnode)
Copy the code
When two old and two new heads are the same, what do you do
Following our first step, do not move the DOM to complete the update
But patchVnode
To continue processing the children of the same two nodes, or update the text
Since we’re not considering multi-layer DOM structures, we’re done here if the old and new heads are the same
You can go straight to the next cycle
NewStartIdx ++, oldStartIdx ++Copy the code
2 Old tail == new tail
sameVnode(oldEndVnode, newEndVnode)
Copy the code
The same treatment as the head
End to end, jump straight into the next loop
NewEndIdx ++, oldEndIdx ++Copy the code
3 Old head == new tail
sameVnode(oldStartVnode, newEndVnode)
Copy the code
This step does not comply with not moving the DOM, so you have to move the DOM
How do you move it?
The source code looks like this
parentElm.insertBefore(
oldStartVnode.elm,
oldEndVnode.elm.nextSibling
);
Copy the code
Moves in the position of the new child, with the old head at the end of the new child
So put oldStartVnode’s DOM after oldEndVnode
But because there is no way to put the DOM after anyone, insertBefore is the only option
That is, before the node following oldEndVnode
It looks something like this
Then update both indexes
OldStartIdx++ newEndIdx -Copy the code
4 Old tail == new head
sameVnode(oldEndVnode, newStartVnode)
Copy the code
It also doesn’t apply to not moving the DOM, so you can only move the DOM
How do you move it?
parentElm.insertBefore(
oldEndVnode.elm,
oldStartVnode.elm
);
Copy the code
Place the oldEndVnode DOM directly in front of the current oldStartvNode.elm
It looks something like this
Then update both indexes
NewStartIdx++ oldEndIdx -Copy the code
5 Single traversal search
This is a last resort when all four comparisons fail
Take the children of the new child node and go directly through the old child node array to find the same node
The process looks like this
1. Generate a map table with vnode.key as the key of the old child node array
2. Get a child of the new child node array and check whether its key is in the map above
3. If the DOM does not exist, create a DOM 4. If yes, check whether sameVnode existsCopy the code
Let’s talk about it in detail
1 Generate a Map table
So what this map does is basically determine what old child nodes are there
Let’s say your old array of child nodes is
[{
tag:"div", key:1
}, {
tag:"strong", key:2
}, { tag:"span", key:4 }] Copy the code
CreateKeyToOldIdx generates a map oldKeyToIdx
{vnodeKey: array Index}
The attribute name is vNode. key, and the attribute value is the location of the vnode in children
This is true (see Diff – source code version of the related helper function)
oldKeyToIdx = {
1:0. 2:1. 4:2
}
Copy the code
2 Check whether the new child node exists in the old child node array
Get Vnode, the child of the new child node, and then get its key
To match the map table, check whether the nodes are the same
oldKeyToIdx[newStartVnode.key]
Copy the code
3 does not exist in the old child node array
Create the DOM directly and insert it in front of oldStartVnode
createElm(newStartVnode, parentElm, oldStartVnode.elm);
Copy the code
4 is stored in the old child node array
Find the old child node and determine whether the new child node and the old child node are samevnodes
If the same, move it directly to the front of oldStartVnode
If not, create it directly before inserting oldStartVnode
We said there are two processes for comparing child nodes
1. Compare the old and new child nodes
2. After the comparison, process the remaining nodes
Copy the code
Comparing the old and new child nodes has been covered above, and now comes another process, comparing the remaining nodes, as detailed below
Process possible remaining nodes
In updateChildren, after comparing the old and new arrays, it is possible that one of the arrays will have a portion of its nodes left unprocessed, so it needs to be treated uniformly
1 The new child node is traversed
newStartIdx > newEndIdx
Copy the code
When the new child node is traversed, the old child node may remain
So we need to batch delete the remaining old nodes!
That is, iterating through the remaining nodes, deleting the DOM one by one
for (; oldStartIdx <= oldEndIdx; ++oldStartIdx) {
oldCh[oldStartIdx]
.parentNode
.removeChild(el); } Copy the code
2. The traversal of the old child node is complete
oldStartIdx > oldEndIdx
Copy the code
If the old child node is traversed, the new child node may remain
So we have to deal with the rest of the new child nodes
Obviously, the remaining new children do not exist in the old children, so they are all new
for (; newStartIdx <= newEndIdx; ++newStartIdx) {
createElm(
newCh[newStartIdx],
parentElm,
refElm ); } Copy the code
But there is a problem with new, that is, where to plug?
So one of the refElm has become a suspect, look at the source code
var newEnd = newCh[newEndIdx + 1]
refElm = newEnd ? newEnd.elm :null;
Copy the code
RefElm gets the node after newEndIdx
The node that is not currently being processed is newEndIdx
So newEndIdx+1, if it exists, must have been processed
If newEndIdx has never been moved and is always the last bit, then there is no newCh[newEndIdx + 1]
Then refElm is empty, and the remaining new nodes are added to the end of the child of the parent node, equivalent to
for (; newStartIdx <= newEndIdx; ++newStartIdx) {
parentElm.appendChild(
newCh[newStartIdx]
); } Copy the code
If newEndIdx has been moved, it is added one by one in front of refElm, equivalent to
for (; newStartIdx <= newEndIdx; ++newStartIdx) {
parentElm.insertBefore(
newCh[newStartIdx] ,
refElm ); } Copy the code
As shown in figure
Why do you compare
Now that we’ve covered all the Diff, you should get the idea of the Diff
But I forced myself to think about one thing, which is
Why do you compare them like this?
The following is purely personal fantasy ideas, no authority certification, only for reference
All of our comparisons are to find new child nodes that are the same as old child nodes
And the purpose of our comparative treatment is
1, can not move, try not to move
There is no way, but to move
3. If it is impossible, create or delete itCopy the code
First of all, at the beginning of the comparison, it must be in accordance with our first principle not to move, to find nodes that can not move
And head, tail, tail is more in line with our first principle, so it comes at the beginning, well, that makes sense
Then we move on to our second purpose, which is to follow update Dren
Old head new tail comparison, old tail new head comparison, single lookup comparison
I’m starting to wonder, huh? Head to tail comparison to move I know, but why do we have this comparison?
When I can do all the moves in a single search, right?
I thought for a long time about the relationship between head and tail, and thought it might be to avoid extreme consumption??
How do you say?
For example, when we do a head-to-tail comparison, we use a single lookup all together
If the head and tail nodes are the same, a node has to traverse from the beginning to the end to find the same node
This is just too expensive, so the head-to-tail comparison is added to eliminate the cost of extreme cases
Of course, this is just my opinion for reference, although I did do an example test with that said
Add b div to the child node where two head-to-tail comparisons occur
oldCh = ['header'.'span'.'div'.'b']
newCh = ['sub'.'b'.'div'.'strong']
Copy the code
Use Vue to update, compare the update speed, then update ten times, calculate the average
1. A single search was used to complete the search, which took 0.91ms
2. It took 0.853ms to add the head-to-tail comparisonCopy the code
It is faster
This article is formatted using MDNICE