Because Diff algorithm, calculation is the difference of virtual DOM, so first pave a little virtual DOM, understand its structure, and then layer by layer to uncover the veil of Diff algorithm, to help you thoroughly understand the principle of Diff algorithm
Understanding the virtual DOM
The virtual DOM simply means to simulate the DOM structure with JS objects
So how does it emulate the DOM structure with JS objects? Look at an example
<template>
<div id="app" class="container">
<h1>MuHua</h1>
</div>
</template>
Copy the code
The template above is rotated into the virtual DOM like this
{
'div'.props: {id:'app'.class:'container' },
children: [{tag: 'h1'.children:'MuHua'}}]Copy the code
This DOM structure is called a Virtual DOM (Virtual Node), or vNode for short.
This is expressed by turning each tag into an object that can have three attributes: Tag, props, and children
- Tag: Mandatory. That’s the tag. It can also be a component, or a function
- Props: Optional. It’s the properties and methods on this tag
- Children: Optional. It’s the contents of the tag or the child nodes, if it’s a text node it’s a string, if it’s a child node it’s an array. In other words, if children is a string, that means it must be a text node, which must have no children
Why use the virtual DOM? See figure
As you can see, the native DOM has so many properties and events that even creating an empty div is expensive. The point of using the virtual DOM to improve performance is that when the DOM changes, the DIff algorithm is used to compare the DOM before the data changes to calculate the DOM that needs to be changed, 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 other 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, that is, the changed data is loaded into an asynchronous queue for data update, namely patch, which is used to compare new and old VNodes
Recognize the Diff algorithm
Diff algorithm is called patch in Vue. Its core is to refer to Snabbdom and find out the smallest changes through the comparison of old and new virtual DOM (namely the patch process) to conduct DOM operation
There is no patch for extension in Vue1, and each dependency has a separate Watcher to update. When the project scale gets larger, the performance can not keep up. Therefore, in Vue2, in order to improve the performance, we changed to only one Watcher for each component, and when we need to update, How do you find exactly where the changes are in the component? So patch it’s coming
So when was it executed?
Patch is called once on the first rendering of the page and a new VNode is created without further comparison
When the data in the component changes, it fires the setter and notifies Watcher with Notify. The corresponding Watcher notifies the update and executes the update function, which executes the render function to get the new virtual DOM. Then patch is performed to compare the old virtual DOM rendered last time, and the smallest changes are calculated, and then the real DOM, namely the view, is updated according to the smallest changes
So how does it compute? Look at a map
If you have a DOM structure like the one above, how do you calculate the change? The short answer is
- Walk through the old virtual DOM
- Walk through the new virtual DOM
- Then reorder them based on the changes, such as the changes and additions above
However, this would be a big problem. If there are 1000 nodes, it would require 1000³ calculations, which is 1 billion times, which is unacceptable. Therefore, when using the Diff algorithm in Vue or React, they follow the depth-first strategy and make some optimization to calculate the minimum change
Optimization of Diff algorithm
1. Compare only the same level, not across levels
As shown, the Diff process will only compare DOM at the same level as the color box to simplify the number of comparisons, which is the first aspect
2. Compare label names
If the comparison label 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, which is the second aspect of simplifying the comparison times
3. Comparison of the key
If the label name and key are the same, it will be considered as the same node, and the tree structure will not continue to do depth comparison. For example, when we write v-for, we will compare keys, but if we do not write keys, an error will be reported, which is because the Diff algorithm needs to compare keys
There is a particularly common question in the interview, is to ask you to talk about the role of the key, in fact, the test is everyone’s grasp of the virtual DOM and patch details, can reflect our interviewees’ understanding level, so here extend the key
The key role
Let’s say we have a list, and we need to insert an element in the middle, what happens? Look at a map
Li1 and li2 will not be re-rendered, this is not controversial. Li3, li4, and li5 are all re-rendered
When the key is not used or the index of the list is not used, the position relation of each element is index. The result in the figure above directly causes the position relation of the inserted element to be changed to all the following elements, so all the elements will be updated, which is not what we want. We want to render the one element we added, the other four elements do not make any changes, so do not re-render
In the case of a unique key, the position of each element is the key, so let’s look at the case of a unique key
Li3 and LI4 in the figure will not be re-rendered because the element content has not changed and the corresponding position relationship has not changed.
This is why v-for must write the key, and it is not recommended to use the index of the array as the key
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 is very efficient
- Key is a necessary condition for Vue to determine whether two nodes are the same during patch. For example, if the key is not written in the rendering list, Vue may update elements frequently during comparison, which makes the whole patch process inefficient and affects the performance
- You should avoid using array subscripts as keys, because not having a unique key value can cause bugs like the one in the figure above, preventing Vue from distinguishing between them, and, for example, replacing only the internal attributes of the same tag element without triggering the transition effect
- From the source, it can be known that Vue judges whether two nodes are the same mainly by their element types and keys. If the key is not set, it may always think that these two nodes are the same and can only be updated, which will cause a large number of unnecessary DOM update operations, which is obviously not desirable
SRC \core\vdom\ patch.js-35 line sameVnode(), also described below
Diff algorithm core principle – source code
The above said Diff algorithm, in Vue inside is patch, matting so much, the following into the source code to look at this miraculous patch dry what?
patch
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. Each line in the source code also has annotations
It can take four arguments, mostly the first two
- OldVnode: Old virtual DOM node
- Vnode: the new virtual DOM node
- Hydrating: Whether to mix with the real DOM, which is used for server-side rendering, is explained here
- RemoveOnly: transition-group
The main process is as follows:
- Vnode does not exist, oldVnode exists, delete oldVnode
- Vnode exists, oldVnode does not exist, create vnode
- If both exist, the sameVnode function (explained below) is used to compare whether the same node is present
- If it is the same node, patchVnode is used for subsequent comparison of text changes of nodes or changes of child nodes
- If the node is different, mount the vNode to the oldVnode’s parent element
- If the root node of the component is replaced, the parent node is iterated and updated, and the old node is deleted
- For server-side rendering, use hydrating to mix oldVnode with the real DOM
Here is the complete patch function source code, which I have written in the notes
Source address: SRC \core\vdom\ patch.js-700 lines
// Two judgment functions
function isUndef (v: any) :boolean %checks {
return v === undefined || v === null
}
function isDef (v: any) :boolean %checks {
returnv ! = =undefined&& v ! = =null
}
return function patch (oldVnode, vnode, hydrating, removeOnly) {
// If the new vNode does not exist, but the oldVnode does
if (isUndef(vnode)) {
// If oldVnode exists, the oldVnode component unloads hook destroy
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
const insertedVnodeQueue = []
// If oldVnode does not exist, a new vNode must exist, such as the first rendering
if (isUndef(oldVnode)) {
isInitialPatch = true
// Create a new vNode
createElm(vnode, insertedVnodeQueue)
} else {
// All that remains is if the new vNode and oldVnode exist
// Is not an element node
const isRealElement = isDef(oldVnode.nodeType)
// is the element node && is the same node compared by sameVnode.
if(! isRealElement && sameVnode(oldVnode, vnode)) {// If yes, use patchVnode for subsequent comparison.
patchVnode(oldVnode, vnode, insertedVnodeQueue, null.null, removeOnly)
} else {
// If not the same element node
if (isRealElement) {
// const SSR_ATTR = 'data-server-rendered'
// Rendered if it is an element node and has the 'data-server-Rendered' attribute
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
// It is rendered by the server
oldVnode.removeAttribute(SSR_ATTR)
hydrating = true
}
// This judgment is the processing logic of server rendering, that is, blending
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 very long warning message.')}}// function emptyNodeAt (elm) {
// return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
/ /}
// If it is not rendered by the server, or blending fails, an empty comment node is created instead of the oldVnode
oldVnode = emptyNodeAt(oldVnode)
}
// Get the parent of the oldVnode
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
// Create a DOM node based on the new vNode and mount it to 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, and the parent node needs to be updated
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 elements
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 the parent node
ancestor = ancestor.parent
}
}
// Delete the old node if it still exists
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0.0)}else if (isDef(oldVnode.tag)) {
// Otherwise, uninstall the oldVnode directly
invokeDestroyHook(oldVnode)
}
}
}
// Returns the updated node
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}
Copy the code
sameVnode
This is a function to determine if it’s the same node
This function is not long, directly look at the source code
Source address: SRC \core\vdom\ patch.js-35 line
function sameVnode (a, b) {
return (
a.key === b.key && // The key is the same
a.asyncFactory === b.asyncFactory && ( // Is it an asynchronous component
(
a.tag === b.tag && // The tags are the same
a.isComment === b.isComment && // Is not a comment node
isDef(a.data) === isDef(b.data) && // The content data is the same
sameInputType(a, b) // Check whether the input type is the same
) || (
isTrue(a.isAsyncPlaceholder) && // Check whether the placeholder to distinguish asynchronous components exists
isUndef(b.asyncFactory.error)
)
)
)
}
Copy the code
patchVnode
Source address: SRC \core\vdom\ patch.js-501 line
This function is only performed if the new VNode and oldVnode are the same node, mainly to compare node text changes or child node changes
Or first introduce the main process, and then look at the source code, the process is like this:
- If the oldVnode and vnode reference addresses are the same, the node has not changed and is returned directly
- If the oldVnode isAsyncPlaceholder exists, the async component is skipped and returned directly
- If oldVnode and vnode are static nodes with the same key, and vnode is a clone or v-once, copy oldvNode. elm and oldvNode. child to vnode, and return
- If the vNode is not a text node and not a comment
- If both VNode and oldVnode have children and the children are different, updateChildren is called to update the children
- If only vNode has child nodes, addVnodes is called to create child nodes
- If only oldVnode has child nodes, removeVnodes is called to remove the child node
- If vnode text is undefined, delete vnode.elm text
- If vNode is a text node but not the same as oldVnode text content, the text is updated
function patchVnode (
oldVnode, // Old virtual DOM node
vnode, // New virtual DOM node
insertedVnodeQueue, // Insert a queue of nodes
ownerArray, // Node array
index, // The index of the current node
removeOnly / / only in
) {
// The old and new nodes reference the same address, directly return
// If the props are not changed, the subcomponents are not rendered and reused directly
if (oldVnode === vnode) return
// The new vNode 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 commented or V-if, or is an asynchronous function, skip checking the asynchronous component
if (isTrue(oldVnode.isAsyncPlaceholder)) {
if (isDef(vnode.asyncFactory.resolved)) {
hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
} else {
vnode.isAsyncPlaceholder = true
}
return
}
// When the current node is static, so is the key, or when there is a V-once, the assignment is returned directly
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance
return
}
// this is not the case
let i
const data = vnode.data
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode)
}
// Get the list of child elements
const oldCh = oldVnode.children
const ch = vnode.children
if (isDef(data) && isPatchable(vnode)) {
/ / iterates through all attributes, calling the update oldVnode such as class, style, attrs, domProps, events...
// The update hook function is vNode's own hook function
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
// The update hook function here is the function we passed in
if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
}
// If the new node is not a text node, it has child nodes
if (isUndef(vnode.text)) {
// If both old and new nodes have children
if (isDef(oldCh) && isDef(ch)) {
// If the children of the old and new nodes are different, the updateChildren function is executed to compare the children
if(oldCh ! == ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) }else if (isDef(ch)) {
// If the new node has children, the old node has no children
// If the old node is a text node, that is, there is no child node, it is cleared
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, ' ')
// Add child nodes
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
// If the new node has no children and the old node has children, delete it
removeVnodes(oldCh, 0, oldCh.length - 1)}else if (isDef(oldVnode.text)) {
// If the old node is a text node, clear it
nodeOps.setTextContent(elm, ' ')}}else if(oldVnode.text ! == vnode.text) {// 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)
}
}
Copy the code
updateChildren
Source address: SRC \core\vdom\ patch.js-404 line
This is a function to compare the child nodes of the new vNode and oldVnode when the child nodes are different
This is important! This is important!
For example, now there are two lists of child nodes to compare, the main process of comparison is as follows
The loop iterates over two lists, stopping if the start pointer to one of the lists, startIdx, coincides with the end pointer to endIdx
The contents of the loop are: {
- Contrast the new head with the old head
- The new tail contrasts with the old tail
- The new head versus the old tail
- The new tail contrasts with the old head. These four comparisons are shown here
As long as one of the above four judgments is equal, patchVnode will be called to compare node text changes or child node changes, and then move the subscript of comparison to continue the next round of comparison cycle
If none of the above is true, keep looking for the old children with the new start key
- If not, a new node is created
- If found, compare labels to see if they are the same node
- If it is the same node, call patchVnode for subsequent comparison, then insert the node in front of the old start and move the new start subscript to continue the next cycle comparison
- If it is not the same node, a new one is created
}
- If the old vnode completes traversal first, a new vnode that has not been traversal is added
- If the new vnode completes traversal first, the node that the old vnode does not traverse is deleted
Why is there a head to tail, tail to head operation?
Because reverse operations can be quickly detected, the Diff efficiency can be accelerated
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
let oldStartIdx = 0 // Old vNode traversal index
let newStartIdx = 0 // The new vNode traversal index
let oldEndIdx = oldCh.length - 1 // The length of the old vNode list
let oldStartVnode = oldCh[0] // The first child of the old vNode list
let oldEndVnode = oldCh[oldEndIdx] // The last child of the old vNode list
let newEndIdx = newCh.length - 1 // The length of the new VNode list
let newStartVnode = newCh[0] // The first child of the new vNode list
let newEndVnode = newCh[newEndIdx] // The last child of the new vNode list
let oldKeyToIdx, idxInOld, vnodeToMove, refElm
constcanMove = ! removeOnly// Start pointer moves to the right, end pointer moves to the left
// End the loop when the beginning and end Pointers overlap
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
// Old beginning vs. new beginning
} else if (sameVnode(oldStartVnode, newStartVnode)) {
// A recursive call to the same node continues to compare the contents and children of the two nodes
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
// Then move the pointer back one bit, from front to back
// Compare [0] of two lists for the first time, then compare [1]... And the same after
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
// Compare the old end with the new end
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
// Then move the pointer forward one bit, from back to front
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
// Contrast the old beginning with the new ending
} 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 is evaluated from front to back, and the new list is evaluated from back to front
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
// Contrast old end with new beginning
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
// Old lists are evaluated from back to front, new lists are evaluated from front to back, and then compared
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
// There is no hit in any of the above four cases
} else {
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
// Get the new start key, and look in the old children to see if there is a node with the key
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
// There are elements in the new children, but not in the old children
if (isUndef(idxInOld)) {
/// create a new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
// Find the corresponding element in the old children
vnodeToMove = oldCh[idxInOld]
// Check if the tags are the same
if (sameVnode(vnodeToMove, newStartVnode)) {
// Update two identical 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 nodes from newStartIdx to newEndIdx
refElm = isUndef(newCh[newEndIdx + 1])?null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
// Otherwise, the new vNode completes first
} else if (newStartIdx > newEndIdx) {
// Delete old vNodes that are not traversed
removeVnodes(oldCh, oldStartIdx, oldEndIdx)
}
}
Copy the code
Now that the core logic of the entire Diff process is over, let’s take a look at the changes made in Vue 3
The optimization of Vue3
This article source version is Vue2, in Vue3 the whole rewrite Diff algorithm this piece of things, so the source can be said to be completely different, but to do the same thing
Vue3 Diff complete source code analysis is still in writing, released in a few days, here to introduce the first Vue2 optimization part, especially published data is the update performance improved 1.3~2 times, SSR performance improved 2~3 times, let’s see what it has
The main optimization of the virtual DOM is: event caching
The optimization of Diff is mainly
- Add static tags: Vue2 is full Diff, Vue3 is static tag + incomplete Diff
- Static ascension
- The comparison process is optimized using the longest increasing subsequence: Vue2 compares changes in the updateChildren() function, Vue3 compares this logic in the patchKeyedChildren() function, see below
Event caching
Like a button like this with a click event
<button @click="handleClick"> button < / button >Copy the code
Take a look at the results of Vue3 compilation
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"))}Copy the code
Note that onClick will read the cache first, and if the cache does not exist, the incoming event will be stored in the cache, which can be interpreted as a static node. In Vue2, there is no cache, which is dynamic
Static markup
What is a static tag?
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 except class/style
FULL_PROPS = 1 << 4.// 16 Nodes with dynamic keys need to perform a full diff comparison when the key changes
HYDRATE_EVENTS = 1 << 5.// 32 Node 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 are wrapped in fragments)
KEYED_FRAGMENT = 1 << 7.// 128 Fragment with key attribute or partial element node with key
UNKEYEN_FRAGMENT = 1 << 8.// 256 Fragment without key on the child node
NEED_PATCH = 1 << 9.// 512 a node will only compare non-props
DYNAMIC_SLOTS = 1 << 10.// 1024 Dynamic slot
HOISTED = -1.// Static node
BAIL = -2 // Indicates that no optimization is required during Diff
}
Copy the code
What is the use of static tags? See figure
Where is it used? Such as the following code
<div id="app">
<div>MuHua</div>
<p>{{ age }}</p>
</div>
Copy the code
As a result of compiling in Vue2, anyone interested can install vuE-template-Compiler and test it on their own
with(this){
return _c(
'div',
{attrs: {"id":"app"}},
[
_c('div',[_v("MuHua")]),
_c('p',[_v(_s(age))])
]
)
}
Copy the code
The result of compiling in Vue3 looks like this, if you are interested, you can click here to test it yourself
const _hoisted_1 = { id: "app" }
const _hoisted_2 = /*#__PURE__*/_createElementVNode("div".null."MuHua", -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 */)))}Copy the code
Do you see the -1 and 1 in the compilation result above? These are static marks, which are not available in Vue2. During the patch process, this mark will be judged to Diff the optimization process and some static node comparison will be skipped
Static ascension
In the case of Vue2 and Vue3, each time an update is triggered, all elements are recreated regardless of whether they participate in the update, which is the stack below
with(this){
return _c(
'div',
{attrs: {"id":"app"}},
[
_c('div',[_v("MuHua")]),
_c('p',[_v(_s(age))])
]
)
}
Copy the code
Vue3 saves the non-updated element, creates it once, and then re-uses it for each render, such as the one in the above example, which creates it statically and saves it once
const _hoisted_1 = { id: "app" }
const _hoisted_2 = /*#__PURE__*/_createElementVNode("div".null."MuHua", -1 /* HOISTED */)
Copy the code
Then, each time the age is updated, just create the dynamic content and reuse the static content saved above
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 */)))}Copy the code
patchKeyedChildren
Update Dren is performed in Vue2
- Head and head than
- The tail and tail
- Head and tail
- The tail and head than
- There’s no comparison of hits
In Vue3, patchKeyedChildren is
- Head and head than
- The tail and tail
- Move/add/remove based on the longest increasing subsequence
Let’s take an example
- Older children:
[ a, b, c, d, e, f, g ]
- The new children:
[ a, b, f, c, d, e, h, g ]
- First gear and head ratio, find the difference to end the cycle, get
[ a, b ]
- And then I do the tail to tail ratio, and I find the difference and I end the cycle, and I get
[ g ]
- Save the nodes that have not been compared
[ f, c, d, e, h ]
And use newIndexToOldIndexMap to get the corresponding subscript in the array to generate the array[5, 2, 3, 4, -1]
.- 1
If it’s not in the old array, it’s new - And then I take the longest increasing subsequence in the array, which is
[2, 3, 4]
Corresponding node[ c, d, e ]
- And then you just have to put the rest of the nodes based on
[ c, d, e ]
Move/add/delete the location of the
Using the longest increasing subsequence minimizes DOM movement and minimizes DOM manipulation. If you’re interested, check out Leet-Code problem 300 (Longest increasing subsequence)
Past wonderful
- Vue3 7 and Vue2 12 components communication, worth collecting
- What are the latest updates to Vue3.2
- JavaScript advanced knowledge
- 22 high frequency handwritten JavaScript code to learn
- Front-end anomaly monitoring and DISASTER recovery
conclusion
If this article is of any help to you, please give it a thumbs up. Thank you