Recently, I reviewed virtual DOM and Diff and read many materials. I hereby summarize this long article to deepen my understanding of vue. This article makes a detailed analysis of vue's virtual DOM and Diff algorithm. Some of the key parts are illustrated by moving some diagrams from elsewhere (thanks to the drawing boss), and also contains a more detailed interpretation of the source code.
Rendering of real DOM
Before talking about virtual DOM, let's talk about the rendering of real dom.
The process of browser real DOM rendering is roughly divided into the following parts
- Build a DOM tree. Parse and process HTML tags through HTML parser and build them into a DOM tree. When the parser encounters non blocking resources (pictures, css), it will continue to parse. However, if it encounters script tags (especially without async and defer attributes), it will block rendering and stop HTML parsing. This is why it is best to put script tags under the body.
- Build CSSOM tree. Similar to building DOM, the browser will also build style rules into CSSOM. The browser will traverse the rule set in CSS and create a node tree with parent-child and brother relationships according to the CSS selector.
- Build the Render tree. This step associates DOM with CSSOM to determine what CSS rules should be applied to each DOM element. Match all relevant styles to each visible node in the DOM tree, and determine the calculation style of each node according to the CSS cascade. Invisible nodes (head, including nodes with display:none attributes) will not be generated into the Render tree.
- Layout / reflow. The location and size of nodes determined by the browser for the first time is called layout. If the location and size of subsequent nodes change, this step triggers layout adjustment, that is, reflow.
- Paint / repaint. Draws each visual part of an element onto the screen, including text, color, border, shadow, and replaced elements (such as buttons and images). Repaint is triggered if these elements change, such as text, color, border, shadow, etc. To ensure that redrawing is faster than the initial drawing, the drawing on the screen is usually broken down into several layers. Promoting the content to the GPU layer (which can be triggered by tranform, filter, will change and opacity) can improve the performance of rendering and redrawing.
- Compositing. This step merges the layers in the drawing process to ensure that they are drawn to the screen in the correct order and display the correct content.
Why do you need virtual DOM
The above is a DOM rendering process. If the DOM is updated, the DOM needs to be rendered again. If the following situations exist
<body> <div id="container"> <div class="content" style="color: red;font-size:16px;"> This is a container </div> .... <div class="content" style="color: red;font-size:16px;"> This is a container </div> </div> </body> <script> let content = document.getElementsByClassName('content'); for (let i = 0; i < 1000000; i++) { content[i].innerHTML = `This is a content${i}`; // Trigger backflow content[i].style.fontSize = `20px`; } </script>
The DOM needs to be operated for 100w times, and the backflow is triggered for 100w times. Each DOM update will follow the process to update the real DOM without difference. Therefore, it causes a great waste of performance. If there are complex operations in the loop, frequently triggering backflow and redrawing, it is easy to affect the performance and cause jamming. In addition, it should be noted here that virtual DOM does not mean that it is faster than dom. The performance needs to be divided into scenarios. The performance of virtual DOM is positively related to the template size. The comparison process of virtual DOM will not distinguish the size of data. When there are only a few dynamic nodes in the component, the virtual DOM will still traverse the whole vdom, which is one more layer of operation than direct rendering.
<div class="list"> <p class="item">item</p> <p class="item">item</p> <p class="item">item</p> <p class="item">{{ item }}</p> <p class="item">item</p> <p class="item">item</p> </div>
For example, in the above example, virtual dom. Although there is only one dynamic node, the virtual DOM still needs to traverse the class, text, label and other information of the diff whole list. Finally, it still needs DOM rendering. If it's just a DOM operation, just operate a specific Dom and render it. The core value of virtual DOM is that it can describe the real DOM through js, with stronger expressiveness. Through declarative language operation, it provides developers with a more convenient and fast development experience. Moreover, without manual optimization, it ensures the lower limit of performance and higher performance price ratio in most scenarios.
Virtual DOM
Virtual DOM is essentially a js object, which represents the real DOM structure. tag is used to describe tags, props is used to describe attributes, and children is used to represent nested hierarchical relationships.
const vnode = { tag: 'div', props: { id: 'container', }, children: [{ tag: 'div', props: { class: 'content', }, text: 'This is a container' }] } //Corresponding real DOM structure <div id="container"> <div class="content"> This is a container </div> </div>
The update of the virtual DOM will not operate the DOM immediately, but will find out the nodes to be updated through the diff algorithm, update as needed, and save the updated content as a js object. After the update is completed, it will be mounted on the real DOM to realize the real DOM update. Through virtual DOM, three problems of operating real DOM are solved.
- Frequent updates without differences lead to frequent updates of DOM, resulting in performance problems
- Frequent reflow and redraw
- Development experience
In addition, because the virtual DOM stores js objects, it naturally has the ability of cross platform, not just limited to the browser.
advantage
To sum up, the advantages of virtual DOM are as follows
- The DOM does not need to be updated frequently for minor modifications. The diff algorithm of the framework will automatically compare and analyze the nodes that need to be updated, and update them as needed
- Updating data will not cause frequent backflow and redrawing
- Stronger expression and more convenient data update
- It saves js objects and has cross platform capability
Insufficient
Virtual DOM also has disadvantages. When rendering a large number of DOM for the first time, due to the calculation of an additional layer of virtual DOM, it will be slower than innerHTML insertion.
Implementation principle of virtual DOM
It is mainly divided into three parts
- Create node description objects through js
- diff algorithm compares and analyzes the differences between the old and new virtual DOM S
- Update the differential patch to the real dom
Diff algorithm
In order to avoid unnecessary rendering and update on demand, the virtual DOM will use the Diff algorithm to compare the virtual DOM nodes and compare the node differences, so as to determine the nodes to be updated, and then render. vue adopts the strategy of depth first and same layer comparison.
The comparison between the new node and the old node mainly focuses on three things to achieve the purpose of rendering
- Create a new node
- Delete obsolete node
- Update existing nodes
How to compare whether the old and new nodes are consistent?
function sameVnode(a, b) { return ( a.key === b.key && a.asyncFactory === b.asyncFactory && ( ( a.tag === b.tag && a.isComment === b.isComment && isDef(a.data) === isDef(b.data) && sameInputType(a, b) //Processing of input node ) || ( isTrue(a.isAsyncPlaceholder) && isUndef(b.asyncFactory.error) ) ) ) } //Judge whether two nodes are of the same input type function sameInputType(a, b) { if (a.tag !== 'input') return true let i const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type //input type is the same or both types are text return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB) }
It can be seen that whether the two nodes are the same requires comparison of tags, attributes (in vue, data is used to represent the attribute props in vnode) and annotation nodes (isComment). In addition, special processing will be performed when encountering input.
Create a new node
When there are new nodes and there are no old nodes, it means that this is a new content node. Only element nodes, text nodes and annotation nodes can be created and inserted into the DOM.
Delete old node
When the old node has and the new node does not, it means that the new node abandons part of the old node. Deleting a node will delete the child nodes of the old node.
Update node
If both the new node and the old node exist, the new node shall prevail and the old node shall be updated. How to determine whether a node needs to be updated?
- Judge whether the new node is completely consistent with the old node. If it is the same, it does not need to be updated
// Judge whether vnode and oldVnode are exactly the same if (oldVnode === vnode) { return; }
- Judge whether the new node and the old node are static nodes, whether the key is the same, whether it is a clone node (if it is not a clone node, it means that the rendering function has been reset and needs to be re rendered at this time), or whether the once attribute is set. If the conditions are met, replace the componentInstance
// Whether it is a static node, whether the key is the same, whether it is a clone node, or whether the once attribute is set if ( isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === oldVnode.key && (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) ) { vnode.componentInstance = oldVnode.componentInstance; return; }
- Judge whether the new node has text (through the text attribute). If there is text, compare the old nodes at the same level. If the old node text is different from the new node text, adopt the new text content. If the new node has no text, you need to judge the relevant conditions of the child nodes later
//Judge whether the new node has text if (isUndef(vnode.text)) { //If there is no text, process the relevant code of the child node .... } else if (oldVnode.text !== vnode.text) { //Replace old node text with new node text nodeOps.setTextContent(elm, vnode.text) }
Judge the correlation between the new node and the child nodes of the old node. Here can be divided into four cases
- Both the new node and the old node have child nodes
- Only the new node has children
- Only the old node has child nodes
- Neither the new node nor the old node has child nodes
All have child nodes
When there are child nodes, you need to compare the old and new nodes. If they are different, you need to perform a diff operation. In vue, this is the updateChildren method. We will talk about it in detail later. The comparison of child nodes is mainly a two terminal comparison.
//Judge whether the new node has text if (isUndef(vnode.text)) { //When both the old and new nodes have child nodes, if the new and old child nodes are different, the updateChildren method is used to compare the child nodes if (isDef(oldCh) && isDef(ch)) { if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) } } else if (oldVnode.text !== vnode.text) { //Replace old node text with new node text nodeOps.setTextContent(elm, vnode.text) }
Only the new node has children
If only the new node has child nodes, it means that this is a new content. That is, add a child node to the DOM. Before adding a new node, a duplicate key detection will be performed and a reminder will be given. At the same time, if the old node is only a text node and has no child nodes, the text content of the old node needs to be cleared.
//Only the new node has children if (isDef(ch)) { //Check for duplicate key s if (process.env.NODE_ENV !== 'production') { checkDuplicateKeys(ch) } //Clear old node text if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '') //Add new node addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) } //Check for duplicate key s function checkDuplicateKeys(children) { const seenKeys = {} for (let i = 0; i < children.length; i++) { const vnode = children[i] //Each Key of child node const key = vnode.key if (isDef(key)) { if (seenKeys[key]) { warn( `Duplicate keys detected: '${key}'. This may cause an update error.`, vnode.context ) } else { seenKeys[key] = true } } } }
Only the old node has child nodes
Only the old node has, which means that the new node discards the child nodes of the old node, so the child nodes of the old node need to be deleted
if (isDef(oldCh)) { //Delete old node removeVnodes(oldCh, 0, oldCh.length - 1) }
There are no child nodes
At this time, you need to judge the old node text to see whether the old node has text. If so, it will be cleared
if (isDef(oldVnode.text)) { //empty nodeOps.setTextContent(elm, '') }
The overall logic code is as follows
function patchVnode( oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly ) { // Judge whether vnode and oldVnode are exactly the same if (oldVnode === vnode) { return } if (isDef(vnode.elm) && isDef(ownerArray)) { // Clone reuse node vnode = ownerArray[index] = cloneVNode(vnode) } const elm = vnode.elm = oldVnode.elm if (isTrue(oldVnode.isAsyncPlaceholder)) { if (isDef(vnode.asyncFactory.resolved)) { hydrate(oldVnode.elm, vnode, insertedVnodeQueue) } else { vnode.isAsyncPlaceholder = true } return } // Whether it is a static node, whether the key is the same, whether it is a clone node, or whether the once attribute is set if (isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === oldVnode.key && (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) ) { vnode.componentInstance = oldVnode.componentInstance return } let i const data = vnode.data if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { i(oldVnode, vnode) } const oldCh = oldVnode.children const ch = vnode.children if (isDef(data) && isPatchable(vnode)) { //Call the update callback and the update hook for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode) if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode) } //Judge whether the new node has text if (isUndef(vnode.text)) { //When both the old and new nodes have child nodes, if the new and old child nodes are different, the updateChildren method is used to compare the child nodes if (isDef(oldCh) && isDef(ch)) { if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) } else if (isDef(ch)) { //Only the new node has children if (process.env.NODE_ENV !== 'production') { //Duplicate Key detection checkDuplicateKeys(ch) } //Clear old node text if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '') //Add new node addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) } else if (isDef(oldCh)) { //Only the old node has child nodes. Delete the old node removeVnodes(oldCh, 0, oldCh.length - 1) } else if (isDef(oldVnode.text)) { //Both old and new nodes have no child nodes nodeOps.setTextContent(elm, '') } } else if (oldVnode.text !== vnode.text) { //Replace old node text with new node text nodeOps.setTextContent(elm, vnode.text) } if (isDef(data)) { if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode) } }
The flow chart will be clearer
Comparison update of child nodes updateChildren
When both old and new nodes have child nodes, you need to call the updateChildren method to compare and update child nodes. Therefore, in the data, the old and new node sub nodes are saved as two arrays.
const oldCh = [oldVnode1, oldVnode2,oldVnode3]; const newCh = [newVnode1, newVnode2,newVnode3];
The child node update adopts the strategy of double ended comparison. What is double ended comparison? That is, the comparison between the old and new nodes is the strategy of comparing the head and tail elements (there are four kinds of comparison) with each other, and then moving closer to the middle (newStartIdx, increasing with oldStartIdx, and decreasing with oldEndIdx).
Comparison process
Move closer to the middle
Here is a description of the new front, new rear, old front and old rear
- New front refers to the first element in the array of unprocessed child nodes of the new node, which corresponds to the newStartVnode in the vue source code
- After new, it refers to the last element in the array of unprocessed child nodes of the new node, which corresponds to newEndVnode in the vue source code
- Before, it refers to the first element in the array of unprocessed child nodes of the old node, which corresponds to oldStartVnode in vue source code
- After the old node, it refers to the last element in the array of unprocessed child nodes of the old node, which corresponds to oldEndVnode in vue source code
Child node comparison process
Next, the above comparison process and the operations after comparison will be described
- Compare the new node with the old one. If they are the same, update the patchVnode mentioned above, and then step back between the old and new nodes to compare the second item We don't change the way of comparison until we encounter different situations
if (sameVnode(oldStartVnode, newStartVnode)) { // Update child nodes patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx) // One step back for the old and the new oldStartVnode = oldCh[++oldStartIdx] newStartVnode = newCh[++newStartIdx] }
- For the comparison between the new and the old, if they are the same, update pathvnode, and then move forward to compare the former The comparison method will not be changed until there is a difference
if (sameVnode(oldEndVnode, newEndVnode)) { //Update child nodes patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx) // Old and new forward oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] }
- Compare the new front with the old one. If they are the same, update them, and then move the old front to the back of all unprocessed old node arrays to keep the positions of the old front and the new rear consistent. Then both sides move closer to the middle, the new forward and the old backward. If it is different, it will continue to switch the comparison mode
if (sameVnode(oldStartVnode, newEndVnode)) { patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx) //Move and insert the first child node of the old child node array to the last canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)) //Old backward oldStartVnode = oldCh[++oldStartIdx] //New forward newEndVnode = newCh[--newEndIdx]
- The comparison between the new front and the old rear. If they are the same, update them, and then move the old rear to the front of all unprocessed old node arrays. The old rear is consistent with the new front position, and then the new back, the old forward, continue to move closer to the middle. Continue comparing the remaining nodes. If not, the traditional loop traversal lookup is used.
if (sameVnode(oldEndVnode, newStartVnode)) { patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx) //Insert old move back to front canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) //Old forward oldEndVnode = oldCh[--oldEndIdx] //New backward newStartVnode = newCh[++newStartIdx] }
- Loop through the search. If none of the above four are found, the key will be used to find the match.
At this step, for nodes that do not have a key set, the mapping between key and index {key:index} will be established through createKeyToOldIdx for the first time
// For nodes that do not have a key set, the mapping between key and index {key:index} will be established through createKeyToOldIdx for the first time if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
Then compare the key of the new node with the old node to find the location of the node whose key value matches. Note that if the new node does not have a key, the findIdxInOld method will be executed to traverse the matching old node from beginning to end
//Find the location subscript of the new node in the old node through the key of the new node. If the key is not set, the traversal operation will be performed idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) //findIdxInOld method function findIdxInOld(node, oldCh, start, end) { for (let i = start; i < end; i++) { const c = oldCh[i] //Same node subscript found if (isDef(c) && sameVnode(node, c)) return i } }
If the subscript matching the new node and the old node is still not found through the above method, it indicates that this node is a new node, and then perform the new operation.
//If the new node cannot find its own location subscript in the old node, it indicates that it is a new element and performs the add operation if (isUndef(idxInOld)) { createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) }
If it is found, it means that an old node with the same key value or the same node and key is found in the old node. If the nodes are the same, after patchVnode, you need to move the old node to all unprocessed nodes. For nodes with the same key and different elements, they are regarded as new nodes and add them. After execution, the new node moves back one step.
//If the new node cannot find its own location subscript in the old node, it indicates that it is a new element and performs the add operation if (isUndef(idxInOld)) { // New element createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) } else { // A node with the same key value was found in the old node vnodeToMove = oldCh[idxInOld] if (sameVnode(vnodeToMove, newStartVnode)) { // Same child node update operation patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx) // After updating, assign the old node undefined oldCh[idxInOld] = undefined //Move old nodes before all unprocessed nodes canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm) } else { // If it is the same key, different elements will be created as new nodes createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) } } //New node backward newStartVnode = newCh[++newStartIdx]
When the traversal of the old node is completed, but the traversal of the new node has not been completed, it means that the subsequent nodes are new nodes and perform the new operation. If the traversal of the new node is completed and the traversal of the old node has not been completed, it means that the old node has redundant nodes and performs the deletion operation.
//Complete the traversal of the old node, but the new node has not completed the traversal, if (oldStartIdx > oldEndIdx) { refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm // New node addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) } else if (newStartIdx > newEndIdx) { // Redundant old nodes are found and deleted removeVnodes(oldCh, oldStartIdx, oldEndIdx) }
Child node comparison summary
The above is a complete process of comparing and updating child nodes, which is a complete logic code
function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) { let oldStartIdx = 0 let newStartIdx = 0 let oldEndIdx = oldCh.length - 1 let oldStartVnode = oldCh[0] //Before let oldEndVnode = oldCh[oldEndIdx] //Old post let newEndIdx = newCh.length - 1 let newStartVnode = newCh[0] //New front let newEndVnode = newCh[newEndIdx] //New post let oldKeyToIdx, idxInOld, vnodeToMove, refElm // removeOnly is a special flag used only by <transition-group> // to ensure removed elements stay in correct relative positions // during leaving transitions const canMove = !removeOnly if (process.env.NODE_ENV !== 'production') { checkDuplicateKeys(newCh) } //Two terminal comparison traversal while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (isUndef(oldStartVnode)) { //The old front moves backward oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left } else if (isUndef(oldEndVnode)) { // Old rear moving forward oldEndVnode = oldCh[--oldEndIdx] } else if (sameVnode(oldStartVnode, newStartVnode)) { //New and old //Update child nodes patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx) // One step back for the old and the new oldStartVnode = oldCh[++oldStartIdx] newStartVnode = newCh[++newStartIdx] } else if (sameVnode(oldEndVnode, newEndVnode)) { //New and old //Update child nodes patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx) //Take a step forward for the old and the new oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldStartVnode, newEndVnode)) { // New and old //Update child nodes patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx) //Insert old move to last canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)) //New forward, old backward oldStartVnode = oldCh[++oldStartIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldEndVnode, newStartVnode)) { // New front and old back patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx) //Insert old move back to front canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) //New backward, old forward oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] } else { // For nodes that do not have a key set, the mapping between key and index {key:index} will be established through createKeyToOldIdx for the first time if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) //Find the location subscript of the new node in the old node through the key of the new node. If the key is not set, the traversal operation will be performed idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) //If the new node cannot find its own location subscript in the old node, it indicates that it is a new element and performs the add operation if (isUndef(idxInOld)) { // New element createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) } else { // A node with the same key value was found in the old node vnodeToMove = oldCh[idxInOld] if (sameVnode(vnodeToMove, newStartVnode)) { // Same child node update operation patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx) // After updating, assign the old node undefined oldCh[idxInOld] = undefined //Move old nodes before all unprocessed nodes canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm) } else { // If it is the same key, different elements will be created as new nodes createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) } } //New node one step back newStartVnode = newCh[++newStartIdx] } } //Complete the traversal of the old node, but the new node has not completed the traversal, if (oldStartIdx > oldEndIdx) { refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm // New node addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) } else if (newStartIdx > newEndIdx) { // Redundant old nodes are found and deleted removeVnodes(oldCh, oldStartIdx, oldEndIdx) } }
reference material
- VirtualDOM and diff
- Rendering pages: how browsers work
- DOM diff in Vue
- In depth analysis: virtual DOM of Vue core
所有评论(0)