成人怡红院-成人怡红院视频在线观看-成人影视大全-成人影院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號
国产激情精品一区二区三区| 丰满妇女BBWBBWBBWBB| 宝贝儿感受到它对你的爱了吗小说| 免费A级毛片无码免费视频首页| 久久99国产精品99蜜桃| 邻居少妇人妻HD高清大乳在线| 免费无遮挡禁18污污网站| 欧美性BBBBBXXXXX4050免费看| 久久精品国产精品亚洲色婷婷| 久久亚洲日韩看片无码| 女人浓毛巨茎ⅩXXOOO| 日日猛噜噜狠狠扒开双腿小说| 天天躁夜夜躁很很躁| 亚洲AV综合色区无码一二三区 | 国产精品18久久久久久欧美| 国内精品人妻无码久久久影院| 久久精品国产亚洲不AV麻豆| 欧美XXXX做受欧美88| 色欲综合久久躁天天躁| 欧洲熟妇色XXXXⅩ欧美老妇天| 男男gv在线观看| 日本入室强伦姧BD在线观看| 午夜亚洲国产理论片亚洲2020| 亚洲精品乱码久久久久久蜜桃不卡 | 国产无遮挡18禁无码网站免费| 久久久久久久精品无码Av少妇| 欧美人与牲动交XXXⅩ| 丝瓜草莓视频APP| 亚洲人成人网站在线观看| 20岁小伙GAYGAYXⅩX| 高潮VPSWINDOWS国产乱| 大黑大巴大战欧洲美女图片| 国产强奷在线播放| 麻豆一区二区三区蜜桃免费| 麻豆精品传媒一二三区艾秋| 日韩欧美AⅤ综合网站发布| 亚洲AV无码精品蜜桃| 中文字幕伊人久久| 55岁大妈玩CSGO| 国产丰满麻豆VIDEOSSEX| 久久久久亚洲AV成人人电影| 日韩成人无码专区一视频| 亚洲国产精品一区二区第四页 | 激情伊人五月天久久综合| 欧美极品性饥渴少妇大战黑人 | 张柏芝性XXXXXⅩ| 中日双语字幕高清在线观看| 国产99视频精品免费视看9| 国产成人亚洲精品无码青| 久久精品国产亚洲AV高清色欲 | 乱色视频中文字幕| 婷婷久久综合九色综合97| 一下子挺进浓密的黑森林| 多肉到处做的古文| 丰满大屁股BWWBWWBWW| 久久久久久久久无码精品亚洲日韩| 日本公与丰满熄的| 亚洲婷婷五月综合狠狠爱| 在线精品一区二区三区| 国产精品18HDXXXⅩ| 蜜臀AV网站在线观看| 亚洲 欧美 激情 小说 另类| 亚洲男人成人性天堂网站| 厨房挺人ⅩXXX猛交| 巨大黑人XXXXX高潮| 无码专区人妻丝袜| YSL千人千色T9T9T90| 久久99精品久久久久婷婷| 四虎免费影院1515WWW| 亚洲AVAV国产AV综合AV| H精品无码动漫在线观看| 精品久久久久久久无码人妻热| 日日摸夜夜添夜夜添影院 | 亚洲AV永久无码精品无码少妇| 把女人弄爽大黄A大片片| 久久久久亚洲AV成人无码| 忘忧草社区在线影视| A狠狠久久蜜臀婷色中文网| 精品无码久久久久国产| 玩弄少妇秘书人妻系列| YY111111少妇影院无码| 久久婷婷人人澡人人爽人人喊| 欧美大屁股流白浆XXXX视频| 亚洲精品第一国产综合麻豆| 国产对白videos麻豆高潮| 欧美日韩视频一区二区| 夜夜香夜夜摸夜夜添视频| 国产亚洲AV寡妇| 色综合久久蜜芽国产精品 | 日本国产网曝视频在线观看| 最新国产乱人伦偷精品免费网站| 精品3D动画肉动漫在线无码| 无码精品国产一区二区免费 | 无码成A∧人片在线播放| 凹凸国产熟女精品视频| 免费女人高潮流视频在线观看| 亚洲国产成人久久综合碰| 国产精品视频第一区二区三区| 日本大胆欧美人术艺术| AⅤ一区二区三区无卡无码| 久久久久亚洲AV无码专区首 | 7777奇米四色眼影| 久久夜色精品国产亚洲AV动态图| 亚洲成AV人无码综合在线| 国产精品久久久久久久久电影网| 少妇AV一区二区三区无码| 被主人调教边C边打屁股作文| 内射人妻无码色AV| 与亲女洗澡时伦了毛片| 久久精品中文字幕无码| 亚洲娇小被黑人巨大撑爆| 国产一区二区三区不卡在线观看| 免费观看电视剧全集在线播放高清 | 国产女人被狂躁到高潮小说| 无码高潮喷吹在线播放亚洲 | 天天爽夜夜爽人人爽一区二区| 成年女人18级毛片毛片免费| 久久99国产精品久久99果冻传| 欧洲女人牲交视频免费| 无码人妻视频一区二区三区| 一本大道香蕉久中文在线播放| 久久99精品国产麻豆婷婷| 亚洲国产成人久久一区久久| 双腿被绑成M型调教PⅠAY照片 | 毛卡5卡6卡7卡8入口| 亚洲最大的成人网站| 久久国产精品无码网站| 亚洲日韩一区二区三区波多野结衣| 中国少妇精品久久久久无码AV| 久久婷婷国产综合精品| 艳妇乳肉豪妇荡乳ⅩXXOO小说| 久久精品国产亚洲AV麻豆小说| 亚洲日韩在线中文字幕综合| 精品熟女少妇AV免费久久| 亚洲色在线无码国产精品不卡 | 日本一区二区三区免费播放| 草莓视频APP在线下载| 日韩乱码人妻无码中文字幕| 够了够了到高C了| 玩弄朋友娇妻呻吟交换电影| 国产精品亚洲АV无码播放| 欧美劲爆精品白浆视频网站| CHINESE老女人老熟妇| 热RE99久久精品国99热| 吃瓜曝光黑料155FUN| 熟女体下毛毛黑森林| 国产乱人伦精品一区二区在线观看| 青青草无码精品伊人久久蜜臀| 才摸两下小奶头就受不了了| 少洁白妇无删减全文阅读| 国产精品高潮呻吟AV久久无码| 羞羞午夜爽爽爽爱爱爱爱人人人| 国内少妇BBWBBW黑森林| 亚洲人成线无码7777| 乱世桃花免费观看完整版高清| 9色国产深夜内射| 日本ZLJZLJZLJZLJ喷| 国产精品久久久久精品香蕉 | 99国产欧美久久久精品蜜芽| 精品午夜中文字幕熟女人妻在线| 野花日本高清在线观看免费| 免费无码又爽又刺激高潮软件| WWW国产精品内射熟女| 视频二区精品中文字幕| 国产真人无码作爱免费视频APP| 亚洲伊人精品无码一区二区| 国产色诱视频在线观看| 亚洲欧洲第一的日产SUV| 妺妺窝人体色777777换脸| 成年女人18级毛片毛片免费| 亚洲 国产 制服 丝袜 一区| 久久精晶国产99久久6| BRAZZERSHD欧美大屁股| 少妇WWB搡BBBB搡BBBB| 黑巨人的又黑又大又长| 中国老太毛茸茸XXXXHD| 日韩系列 无码迅雷| 国产亚洲AV浪潮A∨尹人Av| 伊人久久精品久久亚洲一区| 人妻少妇精品无码专区漫画| 国产精品毛片A∨一区二区三区| 亚洲熟女少妇一区二区| 青青草国产精品亚洲专区无码| 国产精品免费久久久久久蜜桃| 亚洲熟悉妇女XXX妇女AV| 人妻Av一区二区三区| 国产嫖妓一区二区三区无码| 在线观看韩国电影| 色偷偷AV男人的天堂京东热| 久激情内射婷内射蜜桃| 白嫩无码人妻熟妇啪啪区 | 肉欲麻豆天美传媒| 精品国产福利一区二区| WWW久久只有这里有精品| 校花内裤被涂满了强烈春药 | 最新无码A∨在线观看| 婷婷成人小说综合专区| 麻豆传播媒体免费版官网| 公交车后车座的疯狂的做| 亚洲最大无码成人网站4438|