您好,登錄后才能下訂單哦!
這篇文章主要介紹“vue2的diff算法怎么使用”,在日常操作中,相信很多人在vue2的diff算法怎么使用問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”vue2的diff算法怎么使用”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
比較方式: 同級比較,不會跨級比較
以下源碼來自vue/patch.ts,會有一些提取,相關(guān)函數(shù)會附上鏈接。
diff
過程就是調(diào)用 patch
函數(shù),比較新舊節(jié)點,一邊比較一邊給真實DOM打補(bǔ)丁,那么我們就先來看一下patch
函數(shù):
源碼地址: patch函數(shù),isUndef()函數(shù),isDef()函數(shù), emptyNodeAt函數(shù)
return function patch(oldVnode, vnode, hydrating, removeOnly) { if (isUndef(vnode)) { //新的節(jié)點不存在 if (isDef(oldVnode)) //舊的節(jié)點存在 invokeDestroyHook(oldVnode) //銷毀舊節(jié)點 return } ......... //isRealElement就是為處理初始化定義的,組件初始化的時候,沒有oldVnode,那么Vue會傳入一個真實dom if (!isRealElement && sameVnode(oldVnode, vnode)) { -----判斷是否值得去比較 patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly) ---打補(bǔ)丁,后面會詳細(xì)講 } else { ...... if (isRealElement) ...... oldVnode = emptyNodeAt(oldVnode) //轉(zhuǎn)化為Vnode,并賦值給oldNode } // replacing existing element const oldElm = oldVnode.elm ----找到oldVnode對應(yīng)的真實節(jié)點 const parentElm = nodeOps.parentNode(oldElm) ------找到它的父節(jié)點 createElm(.....) --------創(chuàng)建新節(jié)點 ....遞歸地去更新節(jié)點 return vnode.elm }
其中出現(xiàn)了sameNode
,判斷是否值得我們?nèi)ソo他打補(bǔ)丁,不值得的話就按照上述步驟進(jìn)行替換,我們還是去尋找一下這個函數(shù)
源碼地址: sameNode函數(shù)
function sameVnode(a, b) { return ( a.key === b.key && ----------------------key值相等, 這就是為什么我們推薦要加上key,可以讓判斷更準(zhǔn)確 a.asyncFactory === b.asyncFactory && ((a.tag === b.tag && ---------------------標(biāo)簽相等 a.isComment === b.isComment && ---------是否為注釋節(jié)點 isDef(a.data) === isDef(b.data) && ----比較data是否都不為空 sameInputType(a, b)) || ---------------當(dāng)標(biāo)簽為input的時候,需要比較type屬性 (isTrue(a.isAsyncPlaceholder) && isUndef(b.asyncFactory.error))) ) }
如果值得我們?nèi)ソo他打補(bǔ)丁,則進(jìn)入我們patchVNode
函數(shù)
源碼地址: patchVNode函數(shù)
這個函數(shù)有點長,也是做了一下刪減
function patchVnode(... ) { if (oldVnode === vnode) { //兩個節(jié)點一致,啥也不用管,直接返回 return } .... if ( //新舊節(jié)點都是靜態(tài)節(jié)點,且key值相等,則明整個組件沒有任何變化,還在之前的實例,賦值一下后直接返回 isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === oldVnode.key && (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) ) { vnode.componentInstance = oldVnode.componentInstance return } const oldCh = oldVnode.children //獲取舊節(jié)點孩子 const ch = vnode.children //獲取新節(jié)點孩子 if (isUndef(vnode.text)) { //新節(jié)點沒有文本 if (isDef(oldCh) && isDef(ch)) { //舊節(jié)點孩子和新節(jié)點孩子都不為空 if (oldCh !== ch) //舊節(jié)點孩子不等于新節(jié)點孩子 updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) //重點----比較雙方的孩子進(jìn)行diff算法 } else if (isDef(ch)) { //新節(jié)點孩子不為空,舊節(jié)點孩子為空 .... addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) //新增節(jié)點 } else if (isDef(oldCh)) { //新節(jié)點孩子為空,舊節(jié)點孩子不為空 removeVnodes(oldCh, 0, oldCh.length - 1) //移除舊節(jié)點孩子節(jié)點 } else if (isDef(oldVnode.text)) { //舊節(jié)點文本為不為空 nodeOps.setTextContent(elm, '') //將節(jié)點文本清空 } } else if (oldVnode.text !== vnode.text) { //新節(jié)點有文本,但是和舊節(jié)點文本不相等 nodeOps.setTextContent(elm, vnode.text) //設(shè)置為新節(jié)點的文本 } }
這里的判斷很多,所以我也加了個流程圖
源碼地址:updateChildren函數(shù)
這里采用四步走的形式,我把代碼都拆分出來了
采用的四個指針分別指向四個節(jié)點
oldStartIdx
,newStartIdx
指向舊節(jié)點頭,新節(jié)點頭, 初始值為0
oldEndIdx
, newEndIdx
指向舊節(jié)點尾,新節(jié)點尾,初始值為長度-1
let oldStartIdx = 0 //舊頭指針 let newStartIdx = 0 //新頭指針 let oldEndIdx = oldCh.length - 1 //舊尾指針 let newEndIdx = newCh.length - 1 //新尾指針 let oldStartVnode = oldCh[0] //舊頭結(jié)點 let oldEndVnode = oldCh[oldEndIdx] //舊尾結(jié)點 let newStartVnode = newCh[0] //新頭結(jié)點 let newEndVnode = newCh[newEndIdx] //新尾結(jié)點
舊頭和新頭
舊尾和新尾
舊頭和新尾
舊尾和新頭
注意: 這里只要能夠命中一個,就重開,都不能命中的話再看下一環(huán)節(jié), 而不是繼續(xù)挨個判斷
function updateChildren(){ ·.... //好戲從這里開始看 //只要滿足 舊頭指針<=舊尾指針 同時 新頭指針<= 新尾指針 -- 也可以理解為不能交叉 while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { //這里進(jìn)行一個矯正,是應(yīng)該在循環(huán)的過程中,如果進(jìn)入key表查詢的話復(fù)用后會將舊節(jié)點置空(后面會說),所以這里會對其進(jìn)行一個處理 if (isUndef(oldStartVnode)) { //舊頭結(jié)點為空 oldStartVnode = oldCh[++oldStartIdx] // 往右邊走 } else if (isUndef(oldEndVnode)) { //舊尾結(jié)點為空 oldEndVnode = oldCh[--oldEndIdx] //往左邊走 //step1 } else if (sameVnode(oldStartVnode, newStartVnode)) { //比較舊頭和新頭,判斷是否值得打補(bǔ)丁 patchVnode(...) //打補(bǔ)丁 oldStartVnode = oldCh[++oldStartIdx] //齊頭并進(jìn)向右走 newStartVnode = newCh[++newStartIdx] //齊頭并進(jìn)向右走 //step2 } else if (sameVnode(oldEndVnode, newEndVnode)) { //比較舊尾和新尾, 判斷是否值得打補(bǔ)丁 patchVnode(...) //打補(bǔ)丁 oldEndVnode = oldCh[--oldEndIdx] //齊頭并進(jìn)向左走 newEndVnode = newCh[--newEndIdx] //齊頭并進(jìn)向左走 //step3 } else if (sameVnode(oldStartVnode, newEndVnode)) { //比較舊頭和新尾,判斷是否值得打補(bǔ)丁 patchVnode(...) //打補(bǔ)丁 //補(bǔ)完移動節(jié)點 canMove && nodeOps.insertBefore(parentElm,oldStartVnode.elm,nodeOps.nextSibling(oldEndVnode.elm)) oldStartVnode = oldCh[++oldStartIdx] //舊頭向右走 newEndVnode = newCh[--newEndIdx] //新尾向左走 //step4 } else if (sameVnode(oldEndVnode, newStartVnode)) { //比較舊尾和新頭,判斷是否值得打補(bǔ)丁 patchVnode(...) //打補(bǔ)丁 //補(bǔ)完移動節(jié)點 canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) oldEndVnode = oldCh[--oldEndIdx] //舊尾向左走 newStartVnode = newCh[++newStartIdx] //新頭向右走 }
實踐來一下,就拿上面隨機(jī)來的例子吧
step1 、step2
step3 、step4(命中)
在step4進(jìn)行處理,移動節(jié)點到正確位置(插在舊頭的前面)
舊尾向左走,新頭向右走
處理完后就重開,從step1開始,到step2再次命中,此時oldEndInx
和newEndInx
齊頭并進(jìn)向左走(注意這里是不用去移動節(jié)點的哦)(左), 然后重開,在step2再次命中...(右)
重開, 這次在step3命中,然后將舊頭結(jié)點結(jié)點的真實節(jié)點插在舊尾結(jié)點的后面,到這里其實真實節(jié)點就已經(jīng)是我們所期望的了
上述處理完后,舊頭向右走,新尾向左走,命中step1,新頭和舊頭都向左走,出現(xiàn)交叉情況,至此退出循環(huán)
通過上面這個例子,我們把四種情況都命中了一下(一開始隨便畫的圖沒想到都命中了哈哈哈),也成功通過復(fù)用節(jié)點將真實結(jié)點變?yōu)轭A(yù)期結(jié)果,這里便是雙端diff一個核心體現(xiàn)了
但是如果四種情況都沒有命中的呢(如圖下)
則會走向我們最后一個分支,也就是后面介紹的列表尋找
先來看懂里面涉及到的createKeyToOldIdx
函數(shù)
源碼地址: createKeyToOldIdx函數(shù)
function createKeyToOldIdx(children, beginIdx, endIdx) { let i, key const map = {} //初始化一個對象 for (i = beginIdx; i <= endIdx; ++i) { //從頭到尾 key = children[i].key //提取每一項的key if (isDef(key)) map[key] = i //key不為空的時候,存入對象,鍵為key,值為下標(biāo) } return map //返回對象 } //所以該函數(shù)的作用其實就是生成了一個節(jié)點的鍵為key,值為下標(biāo)的一個表
再來看一下里面涉及到的findIdxInOld
函數(shù)
源碼地址:findIdxInOld函數(shù)
function findIdxInOld(node, oldCh, start, end) { //其實就是進(jìn)行了一個遍歷的過程 for (let i = start; i < end; i++) { const c = oldCh[i] if (isDef(c) && sameVnode(node, c)) return i //判斷是否有值得打補(bǔ)丁的節(jié)點,有則返回 } }
進(jìn)入正文
let oldKeyToIdx, idxInOld, vnodeToMove, refElm; .... else { if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) //傳入的是舊節(jié)點孩子,所以生成了一個舊節(jié)點孩子的key表 //使用三目運算符--- 這里也是要使用key的原因,key有效的話可以通過表獲取,無效的話則得進(jìn)行比遍歷比較 idxInOld = isDef(newStartVnode.key) //判斷新頭結(jié)點的key是否不為空--是否有效 ? oldKeyToIdx[newStartVnode.key] //不為空的的話就到key表尋找該key值對象的舊節(jié)點的下標(biāo) : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) //遍歷尋找舊節(jié)點數(shù)組中是否有和新頭結(jié)點值得打補(bǔ)丁的節(jié)點,有的話則賦值其下標(biāo)給idxInOld(不通過key) if (isUndef(idxInOld)) { //發(fā)現(xiàn)找不到了就直接創(chuàng)建新真實節(jié)點 createElm(...) } else { //找到了 vnodeToMove = oldCh[idxInOld] //找到該下標(biāo)對應(yīng)的節(jié)點 if (sameVnode(vnodeToMove, newStartVnode)) { //進(jìn)行一個比較判斷是否值得打補(bǔ)丁 patchVnode(...) //打補(bǔ)丁 oldCh[idxInOld] = undefined //置空,下次生成表就不會把它加進(jìn)去 canMove &&nodeOps.insertBefore( parentElm, vnodeToMove.elm,oldStartVnode.elm ) //移動節(jié)點 } else { //不值得打補(bǔ)丁,創(chuàng)建節(jié)點 createElm(...) } } newStartVnode = newCh[++newStartIdx] //新頭指針向前一步走 } } //--- while循環(huán)到這里
看完源碼其實可以總結(jié)一下,就是前面四個都沒有命中后,就會生成舊節(jié)點孩子的key
表
新頭節(jié)點的key
有效的話,就拿新頭節(jié)點的key
去舊節(jié)點的key
表找,找不到就創(chuàng)建新的真實節(jié)點, 找得到的話就判斷是否值得打補(bǔ)丁,值得的話就打補(bǔ)丁后復(fù)用節(jié)點,然后將該舊節(jié)點孩子值置空,不值得就創(chuàng)建新節(jié)點
新頭節(jié)點的key
無效的話,則去遍歷舊節(jié)點數(shù)組挨個進(jìn)行判斷是否值得打補(bǔ)丁,后續(xù)跟上述一樣
新頭指針向前一步走
也使用一下上面的例子運用一下這個步驟,以下都為key有效的情況
(重新放一下圖,方便看)
生成了一個舊節(jié)點的key表(key為鍵,值為下標(biāo)), 然后newStartVnode
的key
值為B,找到舊節(jié)點孩子該節(jié)點下標(biāo)為1,則去判斷是否直接打補(bǔ)丁,值得的話將該舊節(jié)點孩子置空再在A前面插入B
右圖的表中B沒有變?yōu)閡ndefined是因為表示一開始就生成的,在下次進(jìn)入循環(huán)的時候生成的表才會沒有B
然后將新頭向右走一步,然后重開,發(fā)現(xiàn)前四步依舊沒有命中,此時新頭結(jié)點為B,但是生成的舊節(jié)點表沒有B,故創(chuàng)建新的節(jié)點,然后插入
新頭繼續(xù)向右走,重開,命中step1(如圖左), 之后新頭和舊頭齊頭并進(jìn)向右走, 此時,舊頭指向的undefined
(圖右),直接向右走,重開
發(fā)現(xiàn)此時又都沒有命中, 此時也是生成一個key
表,發(fā)現(xiàn)找不到,于是創(chuàng)建新節(jié)點M插入
然后新頭繼續(xù)向前走,依舊都沒有命中,通過key
表去尋找,找到了D,于是移動插入,舊節(jié)點孩子的D置空,同時新頭向前一步走
走完這一步其實就出現(xiàn)交叉情況了,退出循環(huán),此時如下圖,你會發(fā)現(xiàn),誒,前面確實得到預(yù)期了,可是后面還有一串呢
別急,這就來處理
if (oldStartIdx > oldEndIdx) { //舊的交叉了,說明新增的節(jié)點可能還沒加上呢 refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm addVnodes(....) //新增 } else if (newStartIdx > newEndIdx) { //新的交叉了,說明舊節(jié)點多余的可能還沒刪掉呢 removeVnodes(oldCh, oldStartIdx, oldEndIdx) //把后面那一段刪掉 }
對于上面這個例子,就是把后面那一段,通過遍歷的方式,挨個刪除
Vue具體輕量級框架、簡單易學(xué)、雙向數(shù)據(jù)綁定、組件化、數(shù)據(jù)和結(jié)構(gòu)的分離、虛擬DOM、運行速度快等優(yōu)勢,Vue中頁面使用的是局部刷新,不用每次跳轉(zhuǎn)頁面都要請求所有數(shù)據(jù)和dom,可以大大提升訪問速度和用戶體驗。
到此,關(guān)于“vue2的diff算法怎么使用”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。