This is the third day of my participation in the August Text Challenge.More challenges in August
The principle of DIff algorithm is depth-first and peer traversal comparison, that is, if a node has child nodes, it will continue to recurse the node for comparison (depth-first), and traverse nodes of that level for comparison only when there are no child nodes on the node (peer traversal comparison). Diff algorithm aims to make minimal changes to the old node to achieve unity with the new node. The node is reused through key, and the operation of adding, deleting and modifying cannot be reused.
Source code analysis
updateChildren
// core/vdom/patch.js
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
let oldStartIdx = 0
let 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, 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
constcanMove = ! removeOnlyif(process.env.NODE_ENV ! = ='production') {
checkDuplicateKeys(newCh)
}
// The subscript starts traversing and moves the subscript bidirectionally at the end
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
// //////////////////////////////
// Debug tests
let childEl =Array.from(parentElm.children).map(i= >i.innerText)
/////////////////////////////////
// Four ways to compare old and new nodes
// Check whether the sameVnode() method is the same node. Old nodes can be reused and their children need to be compared
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) { // Compare the old and new node headers
console.log('Comparing oldStartVnode, newStartVnode',oldStartVnode.key, newStartVnode.key)
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) { // Compare the old and new node tails
console.log('Comparing oldEndVnode, newEndVnode',oldEndVnode.key, newEndVnode.key)
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Compare the tail of the new node with the head of the old node
console.log('Comparing oldStartVnode, newEndVnode',oldStartVnode.key, newEndVnode.key)
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Compare the head of the new node with the tail of the old node
console.log('Comparing oldEndVnode, newStartVnode',oldEndVnode.key, newStartVnode.key)
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
// None of the four methods are matched
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // Create a hash table for the nodes in the range of the left and right Pointers of the old node. Key corresponds to index
// Find the old node according to the new node's key.
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key] // If the new node has a key, find the index of the old node corresponding to the key
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) // The new node does not have a key, traverses the old node, find the same node index as the new node
if (isUndef(idxInOld)) { // This node does not exist in the old node
console.log('Node created',newStartVnode.key)
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) // Create a node
} else { // The new node is found in the old node
vnodeToMove = oldCh[idxInOld]
// The key is the same, and the new node needs to be compared
if (sameVnode(vnodeToMove, newStartVnode)) { // Is the same node
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// Just the same key, but not the same node, as a new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
childEl =Array.from(parentElm.children).map(i= >i.innerText)
}
// Node traversal is complete
// Create the remaining nodes
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1])?null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
// Remove the remaining nodes
removeVnodes(oldCh, oldStartIdx, oldEndIdx)
}
}
Copy the code
sameVnode
This function explains the need for the key, which is undefined when not specified, and avoids using index as the key. Here’s why
Let’s say I have several list itemsli
Key is not set or usedindex
, then when comparing the old and new nodes, areundefined
Equal, both are equalindex
It’s going to be equal, then a.key === b.key
This condition must be passed. All the other conditions areli
Tag, so it will also pass, so representssameVnode
Constant for thetrue
So inupdateChildren
Always fired when comparing nodes in a functionsameVnode(oldStartVnode, newStartVnode)
, and then it keeps firingpatchVnode
Function to constantly change the contents of the node. Each node will be changed, not to achieve the effect of the node translation multiplexing.
// core/vdom/patch.js
function sameVnode (a, b) {
return (
a.key === b.key && / / the same key
a.asyncFactory === b.asyncFactory && (
(
a.tag === b.tag && // Same tag
a.isComment === b.isComment && // All comments
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b) // Input tags have the same type
) || (
isTrue(a.isAsyncPlaceholder) &&
isUndef(b.asyncFactory.error)
)
)
)
}
Copy the code
For example, the following nodes are compared in sequence without reuse
Old node: A B D New node: A C B ECopy the code
patchVnode
// core/vdom/patch.js
function patchVnode (oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
/ /... Other code handling
// The new node has no text
if (isUndef(vnode.text)) {
// All have child nodes
if (isDef(oldCh) && isDef(ch)) {
// The child node is different, continue the recursion
if(oldCh ! == ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) }else if (isDef(ch)) { // The new node has child nodes, but the old node does not
if(process.env.NODE_ENV ! = ='production') {
checkDuplicateKeys(ch)
}
// The old node has text, empty the text, and add child nodes to replace the text
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, ' ')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) { // The new node does not have children, but the old node does
removeVnodes(oldCh, 0, oldCh.length - 1) // Remove child nodes from the old node
} else if (isDef(oldVnode.text)) { // There is no child node for either node, and the new node has no text
nodeOps.setTextContent(elm, ' ') // The old node clears the text}}else if(oldVnode.text ! == vnode.text) {// The text of the old and new nodes is different
nodeOps.setTextContent(elm, vnode.text) // Replace the text}}Copy the code
The sample
The old and new nodes are given respectively to execute in a single step:
Old node: A B D New node: A C B ECopy the code
- The first step is to compare nodes A
SameVnode (oldStartVnode, newStartVnode) is triggered. The old and new nodes have the same header pointer, and then the two header Pointers are compared
A B DCopy the code
- Step 2 Create a new node C
If no identical node is found in the first four matching methods, the boundary of the node is determined. The same node may exist in the middle part from the first pointer to the last pointer. At this time, the corresponding node in the old node is obtained according to the key of the new node. C node does not exist, need to create, then new section nod pointer +1
A B C DCopy the code
-
Step 3 Compare node B
Trigger the +1 nod pointer to old and new sections of sameVnode(oldStartVnode, newStartVnode)
A B C DCopy the code
- Step 4 Create node E
This is the same process as creating the C node; The first four matching modes are not triggered, and the same key is not found in the old node. Create a new node C, then head pointer +1
A, B, E, DCopy the code
- Step 5 Remove node D
As can be seen from the section nodding the tail pointer, the head pointer of the new node is larger than the tail pointer at this point, and it is necessary to break out of the while loop. In this case, there are redundant nodes in the old node, which need to be removed
Results: A C B ECopy the code
In the process output by the console, the graffiti part is generated when patchVnode recurses the child nodes of this node, which is not concerned
The articles
- [Vue source]–$nextTick and asynchronous rendering (line by line comments)
- [Vue source]–mixin and extend (line by line)
- [Vue source]–Diff algorithm (line by line comment)
- [Vue source]– How to listen for array changes (line by line comment)
- [Vue source]– Reactive (bidirectional binding) principle (line by line comment)
- [Vue source]– what is done for each lifecycle (line by line comments)
- [Vue source]– How to generate virtual DOM (line by line comment)