1. How does VUE update nodes when data changes?

The overhead of rendering the real DOM is very high. For example, when we modify some data, rendering directly to the real DOM will cause the whole DOM tree to be redrawn and rearranged. Is it possible to update only the small DOM we modify and not the whole DOM? The Diff algorithm helps us.

We first generate a virtual DOM according to the real DOM. When the data of a node of the Virtual DOM is changed, a new Vnode will be generated. Then Vnode and oldVnode will be compared and the differences will be directly modified in the real DOM. Then set oldVnode to Vnode.

Diff calls a function called Patch to compare the old and new nodes and patch the real DOM as it does so.

2. What is the difference between virtual DOM and real DOM?

Virtual DOM is to extract real DOM data and simulate tree structure in the form of objects. For example, dom looks like this:

<div> 
    <p>123</p>
</div>
Copy the code

Corresponding Virtual DOM (pseudocode) :

var Vnode = { 
    tag: 'div', 
    children: [{ 
        tag: 'p', 
        text: '123' 
    }]
};
Copy the code

(Note: Both vNodes and OldvNodes are objects. Remember this.)

3. Diff comparison method?

When the diff algorithm is used to compare the old and new nodes, the comparison will only be performed at the same level, not across levels.

<div> 
    <p>123</p>
</div> 
<div> 
    <span>456</span>
</div>
Copy the code

The code above compares two divs at the same level and p and SPAN at the second level, but it does not compare div and SPAN. A graphic image seen elsewhere:

The flow chart of the diff

When data changes, the set method will call dep. notify to notify all subscribers Watcher, and the subscribers will call Patch to patch the real DOM and update the corresponding view.

Specific analysis: Patch

Let’s see how patch is patched (code only keeps core parts)

function patch (oldVnode, vnode) { // some code if (sameVnode(oldVnode, vnode)) { patchVnode(oldVnode, Vnode)} else {const oEl = oldvNode. el // Let parentEle = api.parentNode(oEl) // Parent element CreateEle (vnode) // Create new element if (parentEle! == null) {api.insertbefore (parentEle, vnode.el, api.nextsibling (oEl)) Oldvnode.el) // Remove old element node oldVnode = null} // some code return vnode}Copy the code

The patch function receives two arguments oldVnode and Vnode representing the new node and the previous old node respectively

Determine whether the two nodes are worthy of comparison, and then execute patchVnode

function sameVnode (a, B) {return (A.cookie === B.cookie && // Key value A.cookie === B.cookie && // Label name A.comment === B.cookie && // Whether it is a comment node // Onclick, style isDef(a.data) === isDef(b.datA) &&sameinputType (a, b) Type must be the same)}Copy the code

If not, replace oldVnode with Vnode

If both nodes are the same, drill down on their children. If the two nodes are different then Vnode has been completely changed and oldVnode can be replaced directly.

What if these two nodes are different but their children are the same? Remember, diff is a layer by layer comparison, if the first layer is not the same then you don’t go into the second layer comparison.

patchVnode

When we determine that the two nodes are worth comparing, we will specify the patchVnode method for both nodes. So what does this method do?

patchVnode (oldVnode, vnode) { const el = vnode.el = oldVnode.el let i, oldCh = oldVnode.children, ch = vnode.children if (oldVnode === vnode) return if (oldVnode.text ! == null && vnode.text ! == null && oldVnode.text ! == vnode.text) { api.setTextContent(el, vnode.text) }else { updateEle(el, vnode, oldVnode) if (oldCh && ch && oldCh ! == ch) { updateChildren(el, oldCh, ch) }else if (ch){ createEle(vnode) //create el's children dom }else if (oldCh){ api.removeChildren(el) } } }Copy the code

This function does the following:

  1. Find the corresponding real DOM, calledel
  2. judgeVnodeoldVnodeWhether it points to the same object,
  3. If so, be directreturnIf they both have text nodes and are not equal, thenelThe text node of theVnodeThe text node of.
  4. ifoldVnodeThere are child nodes andVnodeIf no, delete itelThe child nodes of the
  5. ifoldVnodeThere are no child nodesVnodeYes, it willVnodeAfter the child node of theelExecute if both have child nodesupdateChildrenFunction to compare child nodes, this step is important

The other points are easy to understand, so let’s talk about updateChild in detail

updateChildren

The amount of code is too large to explain line by line, so here are some sample diagrams to describe it.

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) {// For vnode.key comparison, 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 {// Compare keys if (oldKeyToIdx === undefined) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, } 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) } }Copy the code

So what does this function do

  1. willVnodeThe child nodes of theVcholdVnodeThe child nodes of theoldChextracted
  2. oldChvChEach variable has two heads and two tailsStartIdxEndIdx, their two variables are compared with each other, and there are altogether four ways of comparison. If none of the four comparisons match, if you setkey, you will usekeyIn the process of comparing, the variable will move to the middle, onceStartIdx>EndIdxShow thatoldChvChAt least one of them has been traversed, and the comparison ends.

The illustration updateChildren

Finally came to this part, the summary of the above believe that many people also see a face meng force, below we say well. (This is all my own drawing, please recommend a good drawing tool…)

The pink parts are oldCh and vCh

There are four ways to compare samevNodes in oldS, oldE, S, and E. When two of them match, the corresponding node in the real DOM will be moved to the corresponding position in Vnode. This sentence is a bit twisted, for example

  1. If oldS and E match, the first node in the real DOM moves to the end
  2. If oldE and S match, the last node in the real DOM moves to the front, and the two matched Pointers move to the center
  3. If none of the four matches is successful, then traversaloldChildSMatch them one by one, and successful matches move the successful nodes to the front of the real DOMNode corresponding to SInsert the corresponding in the DOMoldSLocation,oldSandSThe needle moves to the center.

With a figure

The first step:

OldS = a, oldE = d; S = a, E = b;Copy the code

OldS matches S, then a node in the DOM is placed in the first one, regardless of the first one already. At this point, the dom position is: A, B, and D

The second step:

OldS = b, oldE = d; S = c, E = b;Copy the code

When oldS and E match, the original node B is moved to the end, because E is the last node and their positions should be the same. This is what is said above: When two of them can match, the corresponding node in the real DOM will be moved to the corresponding position of the Vnode. At this time, the dom position is: A, D, b

Step 3:

OldS = d, oldE = d; S = c, E = d;Copy the code

The position of the DOM is a, D, and B

Step 4:

oldS++;
oldE--;
oldS > oldE;
Copy the code

If the traversal is complete, oldCh completes the traversal first. The remaining vCh nodes are inserted into the real DOM according to their index, and the DOM position is: A, C, D, b

A simulation is completed.

The matching process ends with two conditions:

OldS > oldE means that oldCh is traversed first, so the excess vCh is added to DOM according to index (as shown in the figure above). S > E means that vCh is traversed first, so the excess nodes in the range of [oldS, oldE] are deleted in the real DOM

After sameVnode is successful, patchVnode will be executed, as shown in the above code

if (sameVnode(oldStartVnode, newStartVnode)) { 
    patchVnode(oldStartVnode, newStartVnode)
}
Copy the code

This recurses until all the children of oldVnode and Vnode are compared. All dom patches are in place.