Because the Diff algorithm calculates the difference of virtual DOM, first pave a little bit of virtual DOM, understand its structure, and then uncover the veil of Diff algorithm layer by layer, in simple terms, to help you thoroughly understand the principle of Diff algorithm
Understanding virtual DOM
Virtual DOM is simply to use JS objects to simulate DOM structure
How does it use JS objects to simulate DOM structure? Look at an example
<template> <div id="app" class="container"> <h1>Mu Hua</h1> </div> </template>
The above template is transferred to the virtual DOM, which is the following
{ 'div', props:{ id:'app', class:'container' }, children: [ { tag: 'h1', children:'Mu Hua' } ] }
Such a DOM structure is called virtual DOM (Virtual Node), or vnode for short.
Its expression is to turn each tag into an object, which can have three attributes: tag, props and children
- tag: required. It's the label. It can also be a component or a function
- props: not required. These are the properties and methods on this label
- Children: not required. It is the content or child node of the label. If it is a text node, it is a string. If there is a child node, it is an array. In other words, if you judge that children is a string, it means that it must be a text node, and this node must have no child elements
Why use virtual DOM? Look at a picture
As shown in the figure, the native DOM has many properties and events, and even creating an empty div costs a lot. The point of using virtual DOM to improve performance is to calculate the DOM to be changed by comparing the diff algorithm with the DOM before data change, and then only operate on the changed DOM instead of updating the whole view
How to convert DOM into virtual DOM in Vue? If you are interested, you can follow my another article to learn more about the template compilation process and principle in Vue
In Vue, the data update mechanism of virtual DOM adopts asynchronous update queue, which is to load the changed data into an asynchronous queue of data update, that is, patch, which is used to compare new and old vnode s
Cognitive Diff algorithm
Diff algorithm is called patch in Vue, and its core is reference Snabbdom , through the comparison between the old and new virtual DOM (i.e. patch process), find out the place with the smallest change and switch to DOM operation
extend
In Vue1, there is no patch. Each dependency has a separate Watcher to update. When the project scale becomes larger, the performance can't keep up. Therefore, in Vue2, in order to improve the performance, there is only one Watcher for each component. How can we accurately find the location of the change in the component when we need to update? So patch it's coming
So when did it execute?
When the page is rendered for the first time, the patch will be called and a new vnode will be created without deeper comparison
Then, when the data in the component changes, the setter will be triggered, and then Notify the watcher through Notify. The corresponding Watcher will Notify the update and execute the update function. It will execute the render function to obtain the new virtual DOM, and then execute the patch to compare the old virtual dom of the last rendering result and calculate the minimum change, Then update the real DOM, that is, the view, according to this minimum change
So how is it calculated? Look at a picture first
For example, with a DOM structure like the one shown above, how do you calculate the change? Simply put
- Traversing the old virtual DOM
- Traverse the new virtual DOM
- Then reorder according to changes, such as changes and additions above
But this will have a big problem. If there are 1000 nodes, you need to calculate 1000 ³ Times, that is, 1 billion times, which is unacceptable. Therefore, when using the Diff algorithm in Vue or React, it follows the depth first strategy and makes some optimization based on the same layer comparison strategy to calculate the minimum change
Optimization of Diff algorithm
1. Compare only the same level, not cross level
As shown in the figure, the Diff process will only compare the DOM of the same level framed with the same color, so as to simplify the comparison times. This is the first aspect
2. Compare tag names
If the comparison tag names of the same level are different, the nodes corresponding to the old virtual DOM will be removed directly, and the depth comparison will not continue according to the tree structure. This is the second aspect of simplifying the number of comparisons
3. Compare key
If the tag names are the same and the keys are the same, it will be considered as the same node, and we will not continue to make depth comparison according to this tree structure. For example, when we write v-for, we will compare the keys, and if we do not write the key, an error will be reported, which is because the Diff algorithm needs to compare the keys
There is a very common question in the interview, which is to let you talk about the role of key. In fact, it tests everyone's mastery of the details of virtual DOM and patch, which can reflect the understanding level of our interviewers, so here's an extension of key
Role of key
For example, there is a list. We need to insert an element in the middle. What will happen? Look at a picture first
As shown in the figure, li1 and li2 will not be re rendered, which is not controversial. li3, li4 and li5 will be re rendered
When we do not use the key or the index of the list as the key, the corresponding location relationship of each element is index. The results in the above figure directly lead to the change of the corresponding location relationship from the inserted element to all the subsequent elements, so all the elements will be updated, which is not what we want, What we want is to render the added element, and do not re render the other four elements without making any changes
In the case of using a unique key, the location relationship of each element is the key. Let's take a look at the case of using a unique key value
In this way, li3 and li4 in the figure will not be re rendered, because the element content has not changed and the corresponding positional relationship has not changed.
This is why v-for must write a key, and it is not recommended to use the index of the array as the key in development
To sum up:
- key is mainly used to update the virtual DOM more efficiently, because it can find the same node very accurately, so the patch process will be very efficient
- When Vue judges whether two nodes are the same during the patch process, key is a necessary condition. For example, if the key is not written when rendering the list, Vue may update elements frequently during comparison, making the whole patch process inefficient and affecting performance
- We should avoid using the array subscript as the key, because if the key value is not unique, it may lead to the bug shown in the figure above, making Vue unable to distinguish it from others. For example, when using the same label element for transition switching, it will only replace its internal attributes without triggering the transition effect
- It can be seen from the source code that Vue judges whether the two nodes are the same, mainly judging their element types and keys. If you do not set the key, you may always think that the two nodes are the same and can only do update operations, resulting in a large number of unnecessary DOM update operations, which is obviously undesirable
If you are interested, you can take a look at the source code: SRC \ core \ vdom \ patch.js - line 35 sameVnode(), which is also described in detail below
Diff algorithm core principle - source code
The Diff algorithm is mentioned above. In Vue, it is a patch, which paves the way. Let's go to the source code to see what this amazing patch has done?
patch
Source address: Src / core / vdom / patch.js - line 700
In fact, patch is a function. Let's first introduce the core process in the source code, and then take a look at the source code of patch. There are comments on each line in the source code
It can receive four parameters, mainly the first two
- oldVnode: old virtual DOM node
- vnode: new virtual DOM node
- hydrating: is it to be mixed with the real DOM? It will be used for server-side rendering. I won't explain it here
- removeOnly: transition group will be used. I won't explain it here
The main process is as follows:
- If vnode does not exist and oldVnode exists, delete oldVnode
- Vnode exists. If oldVnode does not exist, create vnode
- If both exist, compare whether they are the same node through the sameVnode function (detailed explanation later)
- If it is the same node, subsequent comparison of node text changes or child node changes can be made through patchVnode
- If it is not the same node, mount vnode under the parent element of oldVnode
- If the root node of the component is replaced, the parent node is traversed and updated, and then the old node is deleted
- If it is a server-side rendering, mix oldVnode with the real DOM with hydrating
Let's look at the complete patch function source code, which shows that I have written it in the comments
// Two judgment functions function isUndef (v: any): boolean %checks { return v === undefined || v === null } function isDef (v: any): boolean %checks { return v !== undefined && v !== null } return function patch (oldVnode, vnode, hydrating, removeOnly) { // If the new vnode does not exist, but the oldVnode does exist if (isUndef(vnode)) { // If oldVnode exists, call the component unloading hook destroy of oldVnode if (isDef(oldVnode)) invokeDestroyHook(oldVnode) return } let isInitialPatch = false const insertedVnodeQueue = [] // If oldVnode does not exist, the new vnode must exist, such as when rendering for the first time if (isUndef(oldVnode)) { isInitialPatch = true // Create a new vnode createElm(vnode, insertedVnodeQueue) } else { // The rest are new vnodes and oldvnodes // Is it an element node const isRealElement = isDef(oldVnode.nodeType) // Is the element node & & compare the same node through sameVnode (see details later in the function) if (!isRealElement && sameVnode(oldVnode, vnode)) { // If yes, use patchVnode for subsequent comparison (detailed explanation is given later in the function) patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly) } else { // If it's not the same element node if (isRealElement) { // const SSR_ATTR = 'data-server-rendered' // If it is an element node and has the attribute 'data server rendered' if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) { // It is rendered by the server. Delete this attribute oldVnode.removeAttribute(SSR_ATTR) hydrating = true } // In this judgment, the processing logic of server-side rendering is mixing if (isTrue(hydrating)) { if (hydrate(oldVnode, vnode, insertedVnodeQueue)) { invokeInsertHook(vnode, insertedVnodeQueue, true) return oldVnode } else if (process.env.NODE_ENV !== 'production') { warn('This is a long warning message') } } // function emptyNodeAt (elm) { // return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm) // } // If it is not rendered by the server, or the mixing fails, create an empty annotation node to replace oldVnode oldVnode = emptyNodeAt(oldVnode) } // Get the parent node of oldVnode const oldElm = oldVnode.elm const parentElm = nodeOps.parentNode(oldElm) // Create a DOM node based on the new vnode and mount it on the parent node createElm( vnode, insertedVnodeQueue, oldElm._leaveCb ? null : parentElm, nodeOps.nextSibling(oldElm) ) // If the root node of the new vnode exists, that is, the root node has been modified, you need to traverse and update the parent node if (isDef(vnode.parent)) { let ancestor = vnode.parent const patchable = isPatchable(vnode) // Recursively update the elements under the parent node while (ancestor) { // Uninstall all components under the old root node for (let i = 0; i < cbs.destroy.length; ++i) { cbs.destroy[i](ancestor) } // Replace existing element ancestor.elm = vnode.elm if (patchable) { for (let i = 0; i < cbs.create.length; ++i) { cbs.create[i](emptyNode, ancestor) } const insert = ancestor.data.hook.insert if (insert.merged) { for (let i = 1; i < insert.fns.length; i++) { insert.fns[i]() } } } else { registerRef(ancestor) } // Update parent node ancestor = ancestor.parent } } // If the old node still exists, delete the old node if (isDef(parentElm)) { removeVnodes([oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) { // Otherwise, uninstall oldVnode directly invokeDestroyHook(oldVnode) } } } // Returns the updated node invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) return vnode.elm }
sameVnode
Source address: Src / core / vdom / patch.js - line 35
This is a function used to determine whether it is the same node
This function is not long. Just look at the source code
function sameVnode (a, b) { return ( a.key === b.key && // Is key the same a.asyncFactory === b.asyncFactory && ( // Is it an asynchronous component ( a.tag === b.tag && // Is the label the same a.isComment === b.isComment && // Is it a comment node isDef(a.data) === isDef(b.data) && // Is the content data the same sameInputType(a, b) // Determine whether the input type is the same ) || ( isTrue(a.isAsyncPlaceholder) && // Determine whether the placeholder for distinguishing asynchronous components exists isUndef(b.asyncFactory.error) ) ) ) }
patchVnode
Source address: Src / core / vdom / patch.js - line 501
This function is only executed when the new vnode and oldVnode are the same node. It is mainly used to compare the changes of node text or child nodes
Let's first introduce the main process and then look at the source code. The process is as follows:
- If the reference addresses of oldVnode and vnode are the same, it means that the node has not changed and is returned directly
- If the isAsyncPlaceholder of oldVnode exists, it skips the check of asynchronous components and returns directly
- If both oldVnode and vnode are static nodes with the same key, and vnode is a clone node or a node controlled by the v-once instruction, copy oldVnode.elm and oldVnode.child to vnode, and then return
- If vnode is neither a text node nor a comment
- If both vnode and oldVnode have child nodes and the child nodes are different, call updateChildren to update the child nodes
- If only vnode has child nodes, addVnodes is called to create child nodes
- If only oldVnode has a child node, removeVnodes is called to delete the child node
- If the vnode text is undefined, delete the vnode.elm text
- If vnode is a text node but the text content is different from oldVnode, the text is updated
function patchVnode ( oldVnode, // Old virtual DOM node vnode, // New virtual DOM node insertedVnodeQueue, // Queue to insert node ownerArray, // Node array index, // Subscript of current node removeOnly // Only in ) { // The reference addresses of new and old nodes are the same, and they are returned directly // For example, when props is not changed, the sub components are not rendered and reused directly if (oldVnode === vnode) return // The new vnode is a real DOM element if (isDef(vnode.elm) && isDef(ownerArray)) { // clone reused vnode vnode = ownerArray[index] = cloneVNode(vnode) } const elm = vnode.elm = oldVnode.elm // If the current node is annotated or v-if, or is an asynchronous function, skip checking asynchronous components if (isTrue(oldVnode.isAsyncPlaceholder)) { if (isDef(vnode.asyncFactory.resolved)) { hydrate(oldVnode.elm, vnode, insertedVnodeQueue) } else { vnode.isAsyncPlaceholder = true } return } // When the current node is a static node, the key is the same, or when there is v-once, it is directly assigned and returned if (isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === oldVnode.key && (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) ) { vnode.componentInstance = oldVnode.componentInstance return } // Don't worry about hook related let i const data = vnode.data if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { i(oldVnode, vnode) } // Get a list of child elements const oldCh = oldVnode.children const ch = vnode.children if (isDef(data) && isPatchable(vnode)) { // Traverse and call update to update all properties of oldVnode, such as class,style,attrs,domProps,events // The update hook function here is the hook function of vnode itself for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode) // The update hook function here is the function we passed if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode) } // If the new node is not a text node, that is, it has child nodes if (isUndef(vnode.text)) { // If both old and new nodes have child nodes if (isDef(oldCh) && isDef(ch)) { // If the child nodes of the new and old nodes are different, execute the updateChildren function to compare the child nodes if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) } else if (isDef(ch)) { // If the new node has child nodes, it means that the old node has no child nodes // If the old node is a text node, that is, there is no child node, it will be cleared if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '') // Add child node addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) } else if (isDef(oldCh)) { // If the new node has no child nodes and the old node has child nodes, it will be deleted removeVnodes(oldCh, 0, oldCh.length - 1) } else if (isDef(oldVnode.text)) { // If the old node is a text node, it is cleared nodeOps.setTextContent(elm, '') } } else if (oldVnode.text !== vnode.text) { // If the new and old nodes are text nodes and the text is different, update the text nodeOps.setTextContent(elm, vnode.text) } if (isDef(data)) { // Execute postpatch hook if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode) } }
updateChildren
Source address: Src / core / vdom / patch.js - line 404
This is the function of comparing child nodes when the new vnode and oldVnode have child nodes and the child nodes are different
Here is the key, the key!
For example, there are two child node lists for comparison. The main comparison process is as follows
Loop through two lists. The loop stop condition is that the start pointer startIdx and end pointer endIdx of one list coincide
The contents of the cycle are:{
- The new head contrasts with the old one
- The new tail contrasts with the old one
- The new head contrasts with the old tail
- The new tail is compared with the old head. These four comparisons are shown in the figure
As long as one of the above four judgments is equal, call patchVnode to compare the changes of node text or child nodes, and then move the subscript of the comparison to continue the next round of circular comparison
If the above four situations fail to hit, keep taking the key of the new start node to the old children
- If not, create a new node
- If found, compare whether the label is the same node
- If it is the same node, call patchVnode for subsequent comparison, then insert this node in front of the old start, and move the new start subscript to continue the next round of circular comparison
- If it is not the same node, a new node is created
}
- If the old vnode is traversed first, add the nodes that the new vnode has not traversed
- If the new vnode is traversed first, delete the nodes that the old vnode has not traversed
Why is there head to tail and tail to head operation?
Because it can quickly detect the reverse operation and speed up the Diff efficiency
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) { let oldStartIdx = 0 // Subscript of old vnode traversal let newStartIdx = 0 // Subscript of new vnode traversal let oldEndIdx = oldCh.length - 1 // Length of old vnode list let oldStartVnode = oldCh[0] // The first child element of the old vnode list let oldEndVnode = oldCh[oldEndIdx] // The last child element of the old vnode list let newEndIdx = newCh.length - 1 // New vnode list length let newStartVnode = newCh[0] // The first child element of the new vnode list let newEndVnode = newCh[newEndIdx] // The last child element of the new vnode list let oldKeyToIdx, idxInOld, vnodeToMove, refElm const canMove = !removeOnly // Loop. The rule is that the start pointer moves to the right and the end pointer moves to the left // The loop ends when the start and end pointers coincide while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (isUndef(oldStartVnode)) { oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left } else if (isUndef(oldEndVnode)) { oldEndVnode = oldCh[--oldEndIdx] // The old start contrasts with the new start } else if (sameVnode(oldStartVnode, newStartVnode)) { // It is a recursive call from the same node to continue to compare the contents and child nodes of the two nodes patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx) // Then move the pointer back one bit and compare it from front to back // For example, compare [0] of two lists for the first time, and then compare [1]..., which is the same later oldStartVnode = oldCh[++oldStartIdx] newStartVnode = newCh[++newStartIdx] // Comparison between old end and new end } else if (sameVnode(oldEndVnode, newEndVnode)) { patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx) // Then move the pointer forward one bit and compare it from back to front oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] // Contrast the old beginning with the new end } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx) canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)) // The old list takes values from front to back, and the new list takes values from back to front, and then compares them oldStartVnode = oldCh[++oldStartIdx] newEndVnode = newCh[--newEndIdx] // The old end contrasts with the new beginning } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx) canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) // The old list takes values from back to front, and the new list takes values from front to back, and then compares them oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] // The above four situations are missed } else { if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // Get the new key and find out whether a node has this key in the old children idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) // There are in the new children, but the corresponding elements are not found in the old children if (isUndef(idxInOld)) { ///Create a new element createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) } else { // The corresponding element was found in the old children vnodeToMove = oldCh[idxInOld] // Judge if the label is the same if (sameVnode(vnodeToMove, newStartVnode)) { // Update the two same nodes patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx) oldCh[idxInOld] = undefined canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm) } else { // If the label is different, create a new element createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) } } newStartVnode = newCh[++newStartIdx] } } // Oldstartidx > oldendidx indicates that the old vnode is traversed first if (oldStartIdx > oldEndIdx) { // Add the node from newStartIdx to newEndIdx refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) // Otherwise, it means that the new vnode is traversed first } else if (newStartIdx > newEndIdx) { // Delete the nodes that are not traversed in the old vnode removeVnodes(oldCh, oldStartIdx, oldEndIdx) } }
So far, the core logic source code of the whole Diff process is over. Let's take a look at the changes made in Vue 3
Vue3 optimization
The source code version of this article is Vue2. The Diff algorithm is rewritten in Vue3. Therefore, the source code is basically different, but the things to be done are the same
The full Diff source code analysis of Vue3 is still being written and will be released in a few days. First, let's introduce the optimization compared with Vue2. The data released by Youda is that the update performance has been improved by 1.3 ~ 2 times and the ssr performance has been improved by 2 ~ 3 times. Let's see what optimizations are available
- Event caching: event caching can be understood as static
- Add static tag: Vue2 is full Diff, Vue3 is static tag + partial Diff
- Static promotion: saved when a static node is created, and then reused directly
- The longest increment subsequence is used to optimize the comparison process: in Vue2, the changes are compared in the updateChildren() function, and in Vue3, the logic of this block is mainly in the patchKeyedChildren() function. See the following for details
Event cache
For example, a button with a click event
<button @click="handleClick">Button</button>
Let's look at the results after Vue3 is compiled
export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createElementBlock("button", { onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.handleClick && _ctx.handleClick(...args))) }, "Button")) }
Note that onClick will read the cache first. If there is no cache, it will store the incoming events in the cache, which can be understood as becoming a static node. Excellent, but there is no cache in Vue2, which is dynamic
Static tag
What are static tags?
Source address: packages/shared/src/patchFlags.ts
export const enum PatchFlags { TEXT = 1 , // Dynamic text node CLASS = 1 << 1, // 2 dynamic class STYLE = 1 << 2, // 4 dynamic style PROPS = 1 << 3, // 8 dynamic attributes other than class/style FULL_PROPS = 1 << 4, // 16 for nodes with dynamic key attribute, when the key is changed, a complete diff comparison is required HYDRATE_EVENTS = 1 << 5, // 32 nodes with listening events STABLE_FRAGMENT = 1 << 6, // 64 a fragment that does not change the order of child nodes (multiple root elements in a component will be wrapped with fragments) KEYED_FRAGMENT = 1 << 7, // 128 fragment s with key attribute or some child nodes have keys UNKEYEN_FRAGMENT = 1 << 8, // 256 child nodes do not have fragment s of key s NEED_PATCH = 1 << 9, // 512 only non props comparison will be performed for one node DYNAMIC_SLOTS = 1 << 10, // 1024 dynamic slot HOISTED = -1, // Static node BAIL = -2 // Indicates that the Diff process does not require optimization }
What's the use of static tags? Look at a picture
Where is it used? For example, the following code
<div id="app"> <div>Mu Hua</div> <p>{{ age }}</p> </div>
As a result of compiling in Vue2, those interested can install Vue template compiler and test by themselves
with(this){ return _c( 'div', {attrs:{"id":"app"}}, [ _c('div',[_v("Mu Hua")]), _c('p',[_v(_s(age))]) ] ) }
The results compiled in Vue3 are as follows. Those interested can click here Self test
const _hoisted_1 = { id: "app" } const _hoisted_2 = /*#__PURE__*/_createElementVNode("div", null, "Mu Hua", -1 /* HOISTED */) export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createElementBlock("div", _hoisted_1, [ _hoisted_2, _createElementVNode("p", null, _toDisplayString(_ctx.age), 1 /* TEXT */) ])) }
See - 1 and 1 in the above compilation results? These are static tags, which are not available in Vue2. This tag will be judged in the patch process to Diff optimize the process and skip some static node comparison
Static lift
In fact, take the example of Vue2 and Vue3 static tags above. In Vue2, whenever an update is triggered, no matter whether the element participates in the update or not, it will be recreated every time, which is the following pile
with(this){ return _c( 'div', {attrs:{"id":"app"}}, [ _c('div',[_v("Mu Hua")]), _c('p',[_v(_s(age))]) ] ) }
In Vue3, the element that does not participate in the update will be saved, created only once, and then reused every time it is rendered. For example, in the above example, it will be created and saved statically
const _hoisted_1 = { id: "app" } const _hoisted_2 = /*#__PURE__*/_createElementVNode("div", null, "Mu Hua", -1 /* HOISTED */)
Then, each time the age is updated, only the dynamic content is created and the static content saved above is reused
export function render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createElementBlock("div", _hoisted_1, [ _hoisted_2, _createElementVNode("p", null, _toDisplayString(_ctx.age), 1 /* TEXT */) ])) }
patchKeyedChildren
In Vue2, updateChildren will update
- Head to head ratio
- Tail and tail ratio
- Head to tail ratio
- Tail to head ratio
- All missed
In Vue3, patchKeyedChildren is
- Head to head ratio
- Tail and tail ratio
- Move / add / delete based on the longest increasing subsequence
Take an example, for example
- Old children: [a, b, c, d, e, f, g]
- New children: [a, b, f, c, d, e, h, g]
- First, compare the head to the head. If you find a difference, end the cycle and get [a, b]
- Then the tail and tail ratio are carried out. If it is found that there is a difference, the cycle is ended and [g] is obtained
- Save the node [f, c, d, e, h] that has not been compared, and get the corresponding subscript in the array through newIndexToOldIndexMap to generate the array [5, 2, 3, 4, - 1]. If - 1 is not in the old array, it means it is new
- Then take out the longest increasing subsequence in the array, that is, the node [c, d, e] corresponding to [2, 3, 4]
- Then, you only need to move / add / delete other remaining nodes based on the location of [c, d, e]
Using the longest increment subsequence can minimize the movement of DOM and achieve the least DOM operations. If you are interested, go to leet code question 300 (longest increment subsequence) to experience
Previous highlights
- The 7 components of Vue3 communicate with the 12 components of Vue2, which is worth collecting
- What has been updated in the latest Vue3.2
- JavaScript advanced knowledge points
- 22 high-frequency JavaScript handwritten code to understand
- Front end anomaly monitoring and disaster recovery
epilogue
If this article is of little help to you, please give me a praise and support. Thank you