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