Home > Article > Web Front-end > In-depth analysis of the principle of diff algorithm in vue2.x
The diff algorithm is an efficient algorithm that compares tree nodes at the same level, avoiding the need to search and traverse the tree layer by layer. This article will give you an in-depth analysis of the principles of the diff algorithm in vue2.x. I hope it will be helpful to you!
I have read a lot of source code analysis articles and read the source code at least twice. After all, I still want to write it myself as a kind of record and study for myself. Focus on the comments and summary, don’t worry too much about the rest, you will gain more by comparing the summary with the source code review process and comments
After the render function is generated, the mounting method will be called, and diff calculation will be performed during mounting. During initialization, since there is no old virtual node, the real dom node will be compared with the virtual node for the first time. For comparison, since the virtual node is not a native node, a replacement operation will be performed for the first time. (Learning video sharing: vue video tutorial)
// /src/core/instance/lifecycle.js Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this const prevEl = vm.$el const prevVnode = vm._vnode const restoreActiveInstance = setActiveInstance(vm) vm._vnode = vnode // 当前render函数产生的虚拟节点,保存后以便下次做对比 if (!prevVnode) { vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false) //初次渲染 } else { vm.$el = vm.__patch__(prevVnode, vnode) } ... }
The main body will have two major branches: front and rear virtual The nodes are consistent, but the front and back virtual nodes are inconsistent
// /src/core/vdom/patch.js export function createPatchFunction (backend) { ... return function patch (oldVnode, vnode, hydrating, removeOnly) { ... if (!isRealElement && sameVnode(oldVnode, vnode)) { ...// 前后虚拟节点一致的方法 } else { ...// 前后虚拟节点不一致的方法 } } }
It is divided into three steps: 1. Create a new node, 2. Update the parent placeholder Node, 3. Delete the old node
When the component is mounted for the first time, the two are different. Later, it will be judged that if it is a real dom, it will be converted into a virtual node and replaced
if (isRealElement) { ... //需要diff 所以将第一次的真实节点转换成虚拟节点 oldVnode = emptyNodeAt(oldVnode) //<div id="app"></div> } // 拿到父类的dom节点 const oldElm = oldVnode.elm //app const parentElm = nodeOps.parentNode(oldElm) // body //创建新dom节点 内部包含组件逻辑 createElm( vnode, insertedVnodeQueue, oldElm._leaveCb ? null : parentElm, nodeOps.nextSibling(oldElm) ) //更新父的占位符节点 (组件更新相关) if (isDef(vnode.parent)) { // 在生成render函数时会生成占位符节点<Dialog>提示</Dialog> => <div>提示</div> <Dialog></Dialog>就是占位符节点 let ancestor = vnode.parent // 判断是否可挂载 const patchable = isPatchable(vnode) while (ancestor) { for (let i = 0; i < cbs.destroy.length; ++i) { cbs.destroy[i](ancestor) } //更新父占位符的element ancestor.elm = vnode.elm if (patchable) { ... } else { registerRef(ancestor) } ancestor = ancestor.parent } } // 删除旧节点 if (isDef(parentElm)) { removeVnodes([oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode) }
function patchVnode (oldVnode,vnode,insertedVnodeQueue,ownerArray,index,removeOnly) { const elm = vnode.elm = oldVnode.elm let i const data = vnode.data // 是组件vnode,在组件更新会调用组件的prepatch方法 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)) { 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) } // 是否是text if (isUndef(vnode.text)) { // 新旧节点都有children if (isDef(oldCh) && isDef(ch)) { if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) // 新有 老没有 children 循环创建新节点 } else if (isDef(ch)) { if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '') addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) // 新没有 老有 children 直接删除老节点 } else if (isDef(oldCh)) { removeVnodes(oldCh, 0, oldCh.length - 1) // 新老都没有 children 老的是文本 就置为空 } else if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, '') } // 是text 直接设置文本 } else if (oldVnode.text !== vnode.text) { nodeOps.setTextContent(elm, vnode.text) } if (isDef(data)) { if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode) } }
Comparison of old and new nodes with children
// /src/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 const canMove = !removeOnly // 满足新节点开始索引小于新节点结束索引,旧节点开始索引小于旧节点结束索引 while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (isUndef(oldStartVnode)) { // 是否定义老节点开始元素 oldStartVnode = oldCh[++oldStartIdx] } else if (isUndef(oldEndVnode)) {// 是否定义老节点结束元素 oldEndVnode = oldCh[--oldEndIdx] // 头(旧节点开始元素)头(新节点开始元素)对比 例如四个li,末尾新增一个li,这种情况头头对比性能高 } else if (sameVnode(oldStartVnode, newStartVnode)) { // sameVnode判断key和tag是否相同 patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx) oldStartVnode = oldCh[++oldStartIdx] newStartVnode = newCh[++newStartIdx] } else if (sameVnode(oldEndVnode, newEndVnode)) { // 尾尾对比 例如四个li,头部新增一个li,这种情况尾尾对比性能高 patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx) oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldStartVnode, newEndVnode)) {// 头尾对比 节点反转优化 reverse 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)) { // 尾头对比 patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx) canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] } else { // 乱序对比(核心diff,其他方式为优化) if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) if (isUndef(idxInOld)) { createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) } else { vnodeToMove = oldCh[idxInOld] if (sameVnode(vnodeToMove, newStartVnode)) { patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx) oldCh[idxInOld] = undefined canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm) } else { createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) } } newStartVnode = newCh[++newStartIdx] } } // 多出来的新节点直接做插入 多出来的旧节点删除 if (oldStartIdx > oldEndIdx) { refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) } else if (newStartIdx > newEndIdx) { removeVnodes(oldCh, oldStartIdx, oldEndIdx) } }
When mounting, the template will be updated after going through the diff algorithm. For the first time, the real dom node will be compared with the generated virtual node, and the generated virtual node will be stored for subsequent updates and comparisons. The diff algorithm only needs to be divided into two branches, the front and rear virtual nodes are consistent and the front and rear virtual nodes are inconsistent. When the front and rear virtual nodes are inconsistent, a new node will be created, the parent placeholder will be updated, and the old node will be deleted. If the old node is a real node, convert it to a virtual node, get the parent node of the old node and replace the old node. When the front and rear virtual nodes are consistent, it will first determine whether the new node is text, and if the value is added directly, if not compare the attributes first, and then determine if the new node has children and the old node has no children, the new node children will be added directly. If the new node has children, the new node will be added directly. If the node does not exist but the old node does, the children of the old node will be removed. If the old and new nodes have children, double pointers will be used to compare the same layer, through head-to-head comparison, tail-to-tail comparison, head-to-tail comparison, tail-to-head comparison, and out-of-order comparison. Continuously iteratively judge and update them to maximize the use of old nodes. If there are redundant new nodes, they will be added, and redundant old nodes will be deleted. Finally, the compared virtual nodes will be returned and stored as the next time Old node to compare to.
patchVnode
method for deep comparison, and then move the index to the next elementpatchVnode
method for deep comparison, and then move the index to the previous element patchVnode
method recursively for deep comparison. Then move the old node element to the end, move the head pointer of the old node to the next one, and move the tail pointer of the new node to the previous one. For example, old: A, B, C, new: C, B, A. For the first comparison, move the old A to the back of C patchVnode
method recursively for deep comparison. Then move the old node element to the front, move the tail pointer of the old node to the previous one, and move the head pointer of the new node to the next one. For example, old: A, B, C, new: C, B, A, D. For the first comparison, the old C is moved to the front of A. (Learning video sharing: web front-end development, Basic programming video)
The above is the detailed content of In-depth analysis of the principle of diff algorithm in vue2.x. For more information, please follow other related articles on the PHP Chinese website!