成人怡红院-成人怡红院视频在线观看-成人影视大全-成人影院203nnxyz-美女毛片在线看-美女免费黄

站長資訊網
最全最豐富的資訊網站

深入剖析vue2.x中diff算法的原理

diff算法是一種通過同層的樹節點進行比較的高效算法,避免了對樹進行逐層搜索遍歷。本篇文章帶大家深入剖析vue2.x中diff算法的原理,希望對大家有所幫助!

深入剖析vue2.x中diff算法的原理

源碼分析文章看了很多,也閱讀了至少兩遍源碼。終歸還是想自己寫寫,作為自己的一種記錄和學習。重點看注釋部分和總結,其余不用太關心,通過總結對照源碼回看過程和注釋收獲更大

更新方法的定義

在生成 render 函數后,就會調用掛載方法,在掛載時就會經過 diff 計算,在初始化的時候,由于沒有舊的虛擬節點,所以初次會將真實的 dom 節點與虛擬節點作對比,由于虛擬節點不是原生節點,所以第一次會做一個替換操作。(學習視頻分享: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)     }    ...   }

diff 算法兩大主要分支

主體會有為兩大分支: 前后虛擬節點一致、前后虛擬節點不一致

// /src/core/vdom/patch.js export function createPatchFunction (backend) {   ...   return function patch (oldVnode, vnode, hydrating, removeOnly) {     ...       if (!isRealElement && sameVnode(oldVnode, vnode)) {         ...// 前后虛擬節點一致的方法       } else {         ...// 前后虛擬節點不一致的方法       }   } }

前后虛擬節點不一致

分為三個步驟: 1.創建新的節點、2.更新父占位符節點、3.刪除舊節點
初次進行掛載組件時兩者不相同,之后會判斷如果是真實dom,就會將其轉為虛擬節點并替換掉

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) }

前后虛擬節點一致

  • 首先判斷新節點是否為文本,是則直接設置文本,不是則繼續判斷
  • 新、舊節點都有children,深度對比(重點)
  • 新節點有children,老節點沒有,循環添加新節點
  • 新節點沒有,老節點有children,直接刪除老節點
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)     }   }

新舊節點都有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)     }   }

注意點

  • key某些情況下不能使用索引,因為改變前后的索引都是一樣的,當在頭部添加元素時,如果用索引做key就會出現更新錯誤問題,vue會理解為在末尾添加一個元素(因為前后的key都是0)
  • 在各種對比情況下,只要找到兩者相同就會去遞歸對比children
  • 在亂序對比中,key的作用是極大的。無key會出現更新出錯問題,同時達不到復用效果
  • diff對比是深度優先,同層比較

總結

在掛載時會經過diff算法后進行模板更新,初次會將真實dom節點和生成的虛擬節點進行對比,并將生成的虛擬節點儲存起來,以便之后更新做對比。diff算法只要分兩發分支,前后虛擬節點一致和前后虛擬節點不一致。當前后虛擬節點不一致時,會創建新節點、更新父占位符、刪除舊節點。如果舊節點是真實節點,就將其轉為虛擬節點,拿到舊節點的父節點后替換舊節點。當前后虛擬節點一致時,會先判斷新節點是否為文本,如果值則直接添加,如果不是先比較屬性,再判斷如果新節點有children,舊節點沒children,就直接添加新節點children,如果新節點沒有,舊節點有,就會將舊節點的children移除,如果新舊節點都有children,利用雙指針同層對比,通過頭頭對比、尾尾對比、頭尾對比、尾頭對比、亂序對比不斷迭代對其進行判斷更新,最大程度的利用舊節點,之后如果有多余的新節點就會將其添加,多余的舊節點將其刪除,最后將對比后的虛擬節點返回儲存起來,作為下次對比的舊節點。

  • 頭頭對比
    如果新舊開始元素是相同vnode,遞歸調用patchVnode方法進行深層對比,之后移動索引至下一個元素
  • 尾尾對比
    如果新舊結束元素是相同vnode,遞歸調用patchVnode方法進行深層對比,之后移動索引至上一個元素
  • 頭尾對比
    將老節點開始元素和舊節點尾元素進行對比,相同就遞歸調用patchVnode方法進行深層對比,之后將舊節點元素移動至最后,舊節點頭指針移動到下一個,新節點的尾指針移動至上一個。例如舊:A,B,C,新:C,B,A,第一次對比將舊A移動到C后邊
  • 尾頭對比
    將老節點尾元素和舊節點開始元素進行對比,相同就遞歸調用patchVnode方法進行深層對比,之后將舊節點元素移動至最前,舊節點尾指針移動到上一個,新節點的頭指針移動至下一個。例如舊:A,B,C,新:C,B,A,D第一次對比將舊C移動到A前邊
  • 亂序對比
    在做比較前會根據key和對應的索引將舊節點生成映射表。在亂序對比時會用當前的key去找舊節點的key,如果能復用,就將節點移動到舊的節點開頭處并遞歸對比children,如果不能復用就創建新的差入到舊的節點開頭處。之后將新的索引移至下一個元素

(學習視頻分享:web前端開發、編程基礎視頻)

贊(0)
分享到: 更多 (0)
?
網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
亚洲国产精品无码久久久秋霞1| 亚洲色成人网站WWW永久| 亚洲AV女人18毛片水真多| 亚洲成色WWW成人网站| 亚洲欧美日韩国产精品一区| 一二三四免费观看在线视频| 中文字幕伊人久久| 波多野结衣乳巨码无在线| 丰满女教师中文字幕5| 国产剧情麻豆女教师在线观看| 荷兰肥妇BBWBBWBBW| 久久毛片免费看一区二区三区| 免费无码又爽又刺激毛片| 人人妻人人澡AV天堂香蕉| 体型差糙汉乖乖女| 亚洲国产成人A精品不卡在线| 一女被两男吃奶玩乳尖| 99热亚洲色精品国产88| 国产AV旡码专区亚洲AV苍井空| 国内精品自国内精品自线| 老汉扛起娇妻玉腿进入| 强奷漂亮雪白丰满少妇| 特级做A爰片毛片免费看| 亚洲国产精品日韩AV专区 | 国产精品无码专区| 久久99亚洲网美利坚合众国| 欧美午夜性春猛交ⅩXXX| 无码人妻丰满熟妇啪啪网站| 亚洲永久精品wW4791一区| 八戒八戒神马影院在线4| 国产美女丝袜一级肛交蜜桃| 久章草在线毛片视频播放| 日产精品乱码卡一卡2卡三网站| 亚洲AV成人片无码网站| 在线成人精品国产区免费| 公车揉捏顶弄伸入| 久久久久久精品成人网站蜜臀 | 午夜一区欧美二区高清三区| 野花韩国在线观看免费版5| 草莓视频IOS下载| 娇小美女被黑壮汉C到喷水| 欧美日韩精品人妻二区| 亚洲ⅤA中文字幕无码| 91人妻人人做人碰人人爽九色| 国产极品熟女沙发内射AV| 久久婷婷人人澡人人爽人人爱| 日韩日韩日韩日韩 日韩 日韩 | 精品人妻人人做人人爽夜夜爽| 欧美人与性口牲恔配视频o| 性色A∨亚洲一区二区三区| 67194熟妇人妻欧美日韩| 国产精品一区在线观看你懂的| 蜜臀久久99精品久久久久久小说| 未满十八岁可以去日本留学吗| 野花香视频免费观看| 国产AV无码区亚洲AV欧美| 噜噜噜亚洲色成人网站∨| 无码国产欧美日韩精品| 99RE66热这里只有精品3| 国内精品国内精品自线在拍| 欧洲另类二三四区| 亚洲欧美日韩中文字幕一区二区三 | 被老外添嫩苞添高潮NP电影| 娇妻被黑人杂交呻吟| 色老99久久精品偷偷鲁| 一本一道波多野结衣AV一区| 国产精品岛国久久久久| 欧美精品多人P群无码| 亚洲成色WWW久久网站| 俄罗斯VIDEODESEXO极| 门卫老头吮她的花蒂| 亚洲AV无码无在线观看红杏| 成人AV无码一区二区三区| 拒嫁豪门少奶奶99次出逃| 午夜精品一区二区三区在线视 | 久久99久久99精品免观看| 十八18禁国产精品WWW| 99久久免费国产精品四虎| 精品欧洲AV无码一区二区男男| 丝瓜草莓榴莲向日葵秋葵| BBW厕所白嫩BBWXXXX| 久久青青草原亚洲AV无码麻豆| 午夜福利AV无码一区二区| 成人H动漫精品一区二区| 免费国产无人区码卡二卡3卡| 亚洲成A人V在线蜜臀| 国产成人综合久久精品推最新 | 免费国精产品一品二品| 亚洲国产中文在线二区三区免| 国产AV一区二区三区天堂综合网| 欧美三级不卡在线播放| 一边喘气一边说疼的视频| 国偷自产AV一区二区三区| 深灬深灬深灬深灬一点| 八戒成年私人影院| 内射人妻无码色AV| 亚洲一区二区三区成人网站| 国产偷窥熟女高潮精品视频| 少妇大叫太大太爽受不了| А√天堂资源中文在线官网| 免费国产黄网站在线观看视频 | 免费无码又爽又刺激高潮软件 | 国产一区二区三区精品视频| 熟女高潮精品一区二区绯乐| 啊灬啊灬别停啊灬用力啊免费| 男男GV在线观看| 野花在线高清视频| 久久不见久久见WWW免费| 亚洲AV永久无码精品天堂动漫 | 亚洲精品无码久久久久久| 国产日产欧产精品精品首页| 天堂AV旡码AV毛片毛片免费| 成人国产精品一区二区网站| 欧美最猛黑人XXXXX猛交| 99精产国品一二三产| 免费观看潮喷到高潮| 中国孕妇变态孕交XXXX| 久久久久久九九99精品| 亚洲无线一二三四区手机| 精东传媒VS天美传媒电影| 亚洲AV五十路在线观看| 国语做受对白XXXXmp4| 亚洲AV无码久久寂寞少妇| 国产在线精品国自产拍影院同性| 无码一区二区波多野结衣播放搜索| 公司办公室的秘书3| 四虎成人WWW成人影院| 国产AV无码专区亚洲AVJUL| 熟妇阿 HD中文电影| 国产AV一区二区三区无码野战| 深入浅出糙汉X软妹V1V| 国产成人无码区免费网站| 玩弄JAPAN白嫩少妇HD| 国产精品亚洲欧美大片在线观看 | 人妻无码一区二区| 成人午夜福利免费无码视频| 色窝窝人妻9色聚色窝| 国产A V无码专区亚洲AV| 天黑黑影院免费观看视频在线播放| 国产成人亚洲综合无码DVD| 无码熟妇人妻AⅤ在线影片| 国产无遮挡无码视频在线观看不卡| 亚洲AV无码精品色夜午夜网址 | 国产精品久久久久久久久久免费 | 无码中文AV波多野吉衣迅雷下载| 国产同性GV男男在线观看| 亚洲国产一区二区三区波多野结衣| 精品熟女AV少妇免费久久自慰| 一本久道中文无码字幕AV| 满熟妇XXXX性久久9久久| BRAZZERSHD欧美大屁股| 日韩人妻无码免费视频一区二区三 | 舌头伸入麻麻两腿之间| 国产精品久久久久永久免费看| 亚洲 日本 欧美 中文幕| 精品无码国产污污污免费网站国产 | 婷婷五月18永久免费网站| 好男人资源在线WWW免费| 野花在线高清视频| 欧美性狂猛BBBBBBXXXXXX| 第二书包网高H肉辣文| 五十老熟女高潮嗷嗷叫| 精品丝袜人妻久久久久久| 111111少妇影院| 日韩AV无码成人精品国产| 国产美女视频国产视视频 | 亚洲AV无码专区在线观看漫画| 久久久久久久99精品免费观看| 99国精产品灬源码1688钻石| 色噜噜影院狠狠狠噜| 国内精品久久久久影院蜜芽| 欲香欲色天天天综合和网| 人与畜禽CROPROATION| 国产精品一区二区久久| 一本无码中文字幕在线观| 人妻丰满熟妇AⅤ无码区在线电影| 国产成人久久精品二区三区| 亚洲国产精品日韩AV不卡在线| 免费无码又爽又刺激激情视频软件| 成人片黄网站A毛片免费| 亚洲AV无码乱码国产麻豆| 蜜桃AV无码乱码精品| 丰满熟妇大肉唇张开| 亚洲欧美在线综合色影视| 欧美群交XXXCOM| 国产精品无码A∨精品影院APP| 夜夜揉揉日日人人| 日韩人妻无码系列专区| 精品人妻一区二区三区视频| JAPANESE极品丰满少妇| 小SAO货水好多真紧H无码视频| 快添捏我奶头我快受不了了动态图| 成人免费乱码大片A毛片| 亚洲国产综合无码一区 | ZZIIZZII亚洲日本少妇| 性生大片免费观看性| 男女狂进狂出动态图| 国产精品久久久久久超碰| 中文字幕人成乱码中国| 无码精品一区二区三区视频色欲网 |