Algoritma diff ialah algoritma cekap yang membandingkan nod pokok pada tahap yang sama, mengelakkan keperluan untuk mencari dan melintasi pokok lapisan demi lapisan. Artikel ini akan memberi anda analisis mendalam tentang prinsip algoritma diff dalam vue2.x saya harap ia akan membantu anda!
Saya telah membaca banyak artikel analisis kod sumber dan membaca kod sumber sekurang-kurangnya dua kali. Lagipun, saya masih mahu menulisnya sendiri sebagai rekod dan kajian untuk diri saya sendiri. Fokus pada ulasan dan ringkasan, jangan terlalu risau tentang yang lain Anda akan mendapat lebih banyak dengan menyemak proses dan ulasan dengan membandingkan ringkasan dengan kod sumber
Selepas fungsi pemaparan dijana, kaedah pelekap akan dipanggil, dan pengiraan perbezaan akan dilakukan semasa pemasangan, kerana tiada nod maya yang lama nod dom sebenar akan dibandingkan dengan nod maya buat kali pertama Sebagai perbandingan, memandangkan nod maya bukan nod asli, operasi penggantian akan dilakukan buat kali pertama. (Belajar perkongsian video: tutorial video vue)
// /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) } ... }
Ibu utama akan menjadi dua utama cawangan: Nod maya hadapan dan belakang adalah konsisten dan nod maya hadapan dan belakang tidak konsisten
// /src/core/vdom/patch.js export function createPatchFunction (backend) { ... return function patch (oldVnode, vnode, hydrating, removeOnly) { ... if (!isRealElement && sameVnode(oldVnode, vnode)) { ...// 前后虚拟节点一致的方法 } else { ...// 前后虚拟节点不一致的方法 } } }
Ia adalah dibahagikan kepada tiga langkah: 1. Buat nod baharu, 2. Kemas kini nod pemegang tempat Induk, 3. Padamkan nod lama
Kedua-duanya berbeza apabila memasang komponen buat kali pertama, ia akan dinilai jika ia ialah dom sebenar, ia akan ditukar menjadi nod maya dan digantikan
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) } }
Perbandingan status kanak-kanak bagi kedua-dua nod lama dan baharu
// /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) } }
Apabila dipasang, templat akan dikemas kini selepas melalui algoritma diff Buat pertama kalinya, nod dom sebenar akan dibandingkan dengan nod maya yang dihasilkan, dan nod maya yang dihasilkan. akan disimpan untuk kemas kini dan perbandingan seterusnya. Algoritma diff hanya perlu dibahagikan kepada dua cabang, nod maya hadapan dan belakang adalah konsisten dan nod maya hadapan dan belakang tidak konsisten. Apabila nod maya hadapan dan belakang tidak konsisten, nod baharu akan dibuat, pemegang tempat induk akan dikemas kini dan nod lama akan dipadamkan. Jika nod lama ialah nod sebenar, tukarkannya kepada nod maya, dapatkan nod induk nod lama dan gantikan nod lama. Apabila nod maya hadapan dan belakang adalah konsisten, ia akan terlebih dahulu menentukan sama ada nod baharu ialah teks, dan jika nilai ditambah secara langsung, jika tidak bandingkan atribut dahulu, dan kemudian tentukan sama ada nod baharu mempunyai anak dan nod lama mempunyai tiada anak, maka anak nod baru akan ditambah secara langsung Jika nod baru mempunyai anak, maka nod baru akan ditambah secara langsung Jika nod lama dan baru mempunyai anak, penunjuk berganda akan digunakan untuk membandingkan lapisan yang sama, melalui perbandingan kepala-ke-kepala, perbandingan ekor-ke-ekor, perbandingan kepala-ke-ekor, perbandingan ekor-ke-kepala, dan perbandingan di luar susunan secara berterusan menilai dan mengemas kininya untuk memaksimumkan penggunaan nod lama yang berlebihan, ia akan ditambah, dan nod lama yang berlebihan akan dipadamkan dikembalikan dan disimpan sebagai nod lama yang akan datang untuk dibandingkan.
patchVnode
secara rekursif untuk perbandingan mendalam, dan kemudian alihkan indeks ke elemen seterusnyapatchVnode
secara rekursif untuk perbandingan mendalam, dan kemudian alihkan indeks ke elemen sebelumnya patchVnode
untuk perbandingan mendalam, kemudian gerakkan yang lama elemen nod ke penghujung, alihkan penuding kepala nod lama ke yang seterusnya, dan alihkan penuding ekor nod baharu ke yang sebelumnya. Contohnya, lama: A, B, C, baharu: C, B, A. Untuk perbandingan pertama, alihkan A lama ke belakang C patchVnode
secara rekursif untuk perbandingan mendalam Kemudian gerakkan elemen nod lama ke hadapan, alihkan penuding ekor nod lama ke yang sebelumnya. dan alihkan penuding kepala nod baharu ke nod seterusnya. Contohnya, lama: A, B, C, baharu: C, B, A, D. Untuk perbandingan pertama, alihkan C lama ke hadapan A (Perkongsian video pembelajaran: pembangunan bahagian hadapan web, Video pengaturcaraan asas)
Atas ialah kandungan terperinci Analisis mendalam tentang prinsip algoritma diff dalam vue2.x. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!