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

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

你了解vue diff算法嗎?原理解析

diff算法是一種通過同層的樹節點進行比較的高效算法,避免了對樹進行逐層搜索遍歷。那么大家對diff算法嗎有多少了解?下面本篇文章就來帶大家深入解析下vue的diff算法,希望對大家有所幫助!

你了解vue diff算法嗎?原理解析

一、是什么

diff 算法是一種通過同層的樹節點進行比較的高效算法。(學習視頻分享:vue視頻教程)

其有兩個特點:

  • 比較只會在同層級進行, 不會跨層級比較
  • 在diff比較的過程中,循環從兩邊向中間比較

diff 算法在很多場景下都有應用,在 vue 中,作用于虛擬 dom 渲染成真實 dom 的新舊 VNode 節點比較

二、比較方式

diff整體策略為:深度優先,同層比較

  • 比較只會在同層級進行, 不會跨層級比較

你了解vue diff算法嗎?原理解析

  • 比較的過程中,循環從兩邊向中間收攏

你了解vue diff算法嗎?原理解析

下面舉個vue通過diff算法更新的例子:

新舊VNode節點如下圖所示:

你了解vue diff算法嗎?原理解析

第一次循環后,發現舊節點D與新節點D相同,直接復用舊節點D作為diff后的第一個真實節點,同時舊節點endIndex移動到C,新節點的 startIndex 移動到了 C

你了解vue diff算法嗎?原理解析

第二次循環后,同樣是舊節點的末尾和新節點的開頭(都是 C)相同,同理,diff 后創建了 C 的真實節點插入到第一次創建的 D 節點后面。同時舊節點的 endIndex 移動到了 B,新節點的 startIndex 移動到了 E

你了解vue diff算法嗎?原理解析

第三次循環中,發現E沒有找到,這時候只能直接創建新的真實節點 E,插入到第二次創建的 C 節點之后。同時新節點的 startIndex 移動到了 A。舊節點的 startIndexendIndex 都保持不動

你了解vue diff算法嗎?原理解析

第四次循環中,發現了新舊節點的開頭(都是 A)相同,于是 diff 后創建了 A 的真實節點,插入到前一次創建的 E 節點后面。同時舊節點的 startIndex 移動到了 B,新節點的startIndex 移動到了 B

你了解vue diff算法嗎?原理解析

第五次循環中,情形同第四次循環一樣,因此 diff 后創建了 B 真實節點 插入到前一次創建的 A 節點后面。同時舊節點的 startIndex移動到了 C,新節點的 startIndex 移動到了 F

你了解vue diff算法嗎?原理解析

新節點的 startIndex 已經大于 endIndex 了,需要創建 newStartIdxnewEndIdx 之間的所有節點,也就是節點F,直接創建 F 節點對應的真實節點放到 B 節點后面

你了解vue diff算法嗎?原理解析

三、原理分析

當數據發生改變時,set方法會調用Dep.notify通知所有訂閱者Watcher,訂閱者就會調用patch給真實的DOM打補丁,更新相應的視圖

源碼位置:src/core/vdom/patch.js

function patch(oldVnode, vnode, hydrating, removeOnly) {     if (isUndef(vnode)) { // 沒有新節點,直接執行destory鉤子函數         if (isDef(oldVnode)) invokeDestroyHook(oldVnode)         return     }      let isInitialPatch = false     const insertedVnodeQueue = []      if (isUndef(oldVnode)) {         isInitialPatch = true         createElm(vnode, insertedVnodeQueue) // 沒有舊節點,直接用新節點生成dom元素     } else {         const isRealElement = isDef(oldVnode.nodeType)         if (!isRealElement && sameVnode(oldVnode, vnode)) {             // 判斷舊節點和新節點自身一樣,一致執行patchVnode             patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)         } else {             // 否則直接銷毀及舊節點,根據新節點生成dom元素             if (isRealElement) {                  if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {                     oldVnode.removeAttribute(SSR_ATTR)                     hydrating = true                 }                 if (isTrue(hydrating)) {                     if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {                         invokeInsertHook(vnode, insertedVnodeQueue, true)                         return oldVnode                     }                 }                 oldVnode = emptyNodeAt(oldVnode)             }             return vnode.elm         }     } }

patch函數前兩個參數位為oldVnodeVnode ,分別代表新的節點和之前的舊節點,主要做了四個判斷:

  • 沒有新節點,直接觸發舊節點的destory鉤子
  • 沒有舊節點,說明是頁面剛開始初始化的時候,此時,根本不需要比較了,直接全是新建,所以只調用 createElm
  • 舊節點和新節點自身一樣,通過 sameVnode 判斷節點是否一樣,一樣時,直接調用 patchVnode去處理這兩個節點
  • 舊節點和新節點自身不一樣,當兩個節點不一樣的時候,直接創建新節點,刪除舊節點

下面主要講的是patchVnode部分

function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {     // 如果新舊節點一致,什么都不做     if (oldVnode === vnode) {       return     }     // 讓vnode.el引用到現在的真實dom,當el修改時,vnode.el會同步變化     const elm = vnode.elm = oldVnode.elm     // 異步占位符     if (isTrue(oldVnode.isAsyncPlaceholder)) {       if (isDef(vnode.asyncFactory.resolved)) {         hydrate(oldVnode.elm, vnode, insertedVnodeQueue)       } else {         vnode.isAsyncPlaceholder = true       }       return     }     // 如果新舊都是靜態節點,并且具有相同的key     // 當vnode是克隆節點或是v-once指令控制的節點時,只需要把oldVnode.elm和oldVnode.child都復制到vnode上     // 也不用再有其他操作     if (isTrue(vnode.isStatic) &&       isTrue(oldVnode.isStatic) &&       vnode.key === oldVnode.key &&       (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))     ) {       vnode.componentInstance = oldVnode.componentInstance       return     }     let i     const data = vnode.data     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)     }     // 如果vnode不是文本節點或者注釋節點     if (isUndef(vnode.text)) {       // 并且都有子節點       if (isDef(oldCh) && isDef(ch)) {         // 并且子節點不完全一致,則調用updateChildren         if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)         // 如果只有新的vnode有子節點       } else if (isDef(ch)) {         if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')         // elm已經引用了老的dom節點,在老的dom節點上添加子節點         addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)         // 如果新vnode沒有子節點,而vnode有子節點,直接刪除老的oldCh       } else if (isDef(oldCh)) {         removeVnodes(elm, oldCh, 0, oldCh.length - 1)         // 如果老節點是文本節點       } else if (isDef(oldVnode.text)) {         nodeOps.setTextContent(elm, '')       }       // 如果新vnode和老vnode是文本節點或注釋節點       // 但是vnode.text != oldVnode.text時,只需要更新vnode.elm的文本內容就可以     } 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)     }   }

patchVnode主要做了幾個判斷:

  • 新節點是否是文本節點,如果是,則直接更新dom的文本內容為新節點的文本內容
  • 新節點和舊節點如果都有子節點,則處理比較更新子節點
  • 只有新節點有子節點,舊節點沒有,那么不用比較了,所有節點都是全新的,所以直接全部新建就好了,新建是指創建出所有新DOM,并且添加進父節點
  • 只有舊節點有子節點而新節點沒有,說明更新后的頁面,舊節點全部都不見了,那么要做的,就是把所有的舊節點刪除,也就是直接把DOM 刪除

子節點不完全一致,則調用updateChildren

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {     let oldStartIdx = 0 // 舊頭索引     let newStartIdx = 0 // 新頭索引     let oldEndIdx = oldCh.length - 1 // 舊尾索引     let newEndIdx = newCh.length - 1 // 新尾索引     let oldStartVnode = oldCh[0] // oldVnode的第一個child     let oldEndVnode = oldCh[oldEndIdx] // oldVnode的最后一個child     let newStartVnode = newCh[0] // newVnode的第一個child     let newEndVnode = newCh[newEndIdx] // newVnode的最后一個child     let oldKeyToIdx, idxInOld, vnodeToMove, refElm     // removeOnly is a special flag used only by <transition-group>     // to ensure removed elements stay in correct relative positions     // during leaving transitions     const canMove = !removeOnly     // 如果oldStartVnode和oldEndVnode重合,并且新的也都重合了,證明diff完了,循環結束     while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {       // 如果oldVnode的第一個child不存在       if (isUndef(oldStartVnode)) {         // oldStart索引右移         oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left       // 如果oldVnode的最后一個child不存在       } else if (isUndef(oldEndVnode)) {         // oldEnd索引左移         oldEndVnode = oldCh[--oldEndIdx]       // oldStartVnode和newStartVnode是同一個節點       } else if (sameVnode(oldStartVnode, newStartVnode)) {         // patch oldStartVnode和newStartVnode, 索引左移,繼續循環         patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)         oldStartVnode = oldCh[++oldStartIdx]         newStartVnode = newCh[++newStartIdx]       // oldEndVnode和newEndVnode是同一個節點       } else if (sameVnode(oldEndVnode, newEndVnode)) {         // patch oldEndVnode和newEndVnode,索引右移,繼續循環         patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)         oldEndVnode = oldCh[--oldEndIdx]         newEndVnode = newCh[--newEndIdx]       // oldStartVnode和newEndVnode是同一個節點       } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right         // patch oldStartVnode和newEndVnode         patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)         // 如果removeOnly是false,則將oldStartVnode.eml移動到oldEndVnode.elm之后         canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))         // oldStart索引右移,newEnd索引左移         oldStartVnode = oldCh[++oldStartIdx]         newEndVnode = newCh[--newEndIdx]       // 如果oldEndVnode和newStartVnode是同一個節點       } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left         // patch oldEndVnode和newStartVnode         patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)         // 如果removeOnly是false,則將oldEndVnode.elm移動到oldStartVnode.elm之前         canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)         // oldEnd索引左移,newStart索引右移         oldEndVnode = oldCh[--oldEndIdx]         newStartVnode = newCh[++newStartIdx]       // 如果都不匹配       } else {         if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)         // 嘗試在oldChildren中尋找和newStartVnode的具有相同的key的Vnode         idxInOld = isDef(newStartVnode.key)           ? oldKeyToIdx[newStartVnode.key]           : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)         // 如果未找到,說明newStartVnode是一個新的節點         if (isUndef(idxInOld)) { // New element           // 創建一個新Vnode           createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)         // 如果找到了和newStartVnodej具有相同的key的Vnode,叫vnodeToMove         } else {           vnodeToMove = oldCh[idxInOld]           /* istanbul ignore if */           if (process.env.NODE_ENV !== 'production' && !vnodeToMove) {             warn(               'It seems there are duplicate keys that is causing an update error. ' +               'Make sure each v-for item has a unique key.'             )           }           // 比較兩個具有相同的key的新節點是否是同一個節點           //不設key,newCh和oldCh只會進行頭尾兩端的相互比較,設key后,除了頭尾兩端的比較外,還會從用key生成的對象oldKeyToIdx中查找匹配的節點,所以為節點設置key可以更高效的利用dom。           if (sameVnode(vnodeToMove, newStartVnode)) {             // patch vnodeToMove和newStartVnode             patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)             // 清除             oldCh[idxInOld] = undefined             // 如果removeOnly是false,則將找到的和newStartVnodej具有相同的key的Vnode,叫vnodeToMove.elm             // 移動到oldStartVnode.elm之前             canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)           // 如果key相同,但是節點不相同,則創建一個新的節點           } else {             // same key but different element. treat as new element             createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)           }         }         // 右移         newStartVnode = newCh[++newStartIdx]       }     }

while循環主要處理了以下五種情景:

  • 當新老 VNode 節點的 start 相同時,直接 patchVnode ,同時新老 VNode 節點的開始索引都加 1
  • 當新老 VNode 節點的 end相同時,同樣直接 patchVnode ,同時新老 VNode 節點的結束索引都減 1
  • 當老 VNode 節點的 start 和新 VNode 節點的 end 相同時,這時候在 patchVnode 后,還需要將當前真實 dom 節點移動到 oldEndVnode 的后面,同時老 VNode 節點開始索引加 1,新 VNode 節點的結束索引減 1
  • 當老 VNode 節點的 end 和新 VNode 節點的 start 相同時,這時候在 patchVnode 后,還需要將當前真實 dom 節點移動到 oldStartVnode 的前面,同時老 VNode 節點結束索引減 1,新 VNode 節點的開始索引加 1
  • 如果都不滿足以上四種情形,那說明沒有相同的節點可以復用,則會分為以下兩種情況:
    • 從舊的 VNodekey 值,對應 index 序列為 value 值的哈希表中找到與 newStartVnode 一致 key 的舊的 VNode 節點,再進行patchVnode,同時將這個真實 dom移動到 oldStartVnode 對應的真實 dom 的前面
    • 調用 createElm 創建一個新的 dom 節點放到當前 newStartIdx 的位置

小結

  • 當數據發生改變時,訂閱者watcher就會調用patch給真實的DOM打補丁
  • 通過isSameVnode進行判斷,相同則調用patchVnode方法
  • patchVnode做了以下操作:
    • 找到對應的真實dom,稱為el
    • 如果都有都有文本節點且不相等,將el文本節點設置為Vnode的文本節點
    • 如果oldVnode有子節點而VNode沒有,則刪除el子節點
    • 如果oldVnode沒有子節點而VNode有,則將VNode的子節點真實化后添加到el
    • 如果兩者都有子節點,則執行updateChildren函數比較子節點
  • updateChildren主要做了以下操作:
    • 設置新舊VNode的頭尾指針
    • 新舊頭尾指針進行比較,循環向中間靠攏,根據情況調用patchVnode進行patch重復流程、調用createElem創建一個新節點,從哈希表尋找 key一致的VNode 節點再分情況操作

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

贊(0)
分享到: 更多 (0)
?
網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
性高湖久久久久久久久| 色窝窝亚洲AV网在线观看| 欧洲成人一区二区三区| 日本XXXX色视频在线播放| 日韩无码视频专区| 无码AV在线一本无码| 亚洲AV无码精品色夜午夜网址 | 日本精品VIDEOSSE×少妇| 少妇特殊按摩高潮爽翻天| 西西人体444WWW高清大但| 亚洲国产成人一区二区三区| 永久看一二三四线| FREE性VIDEOXXⅩ欧美| 宝贝感受到它在爱你吗病娇小说| 国产99视频精品免费视频36| 国内精品久久久久影院一蜜桃| 久久精品人人做人人综合试看| 蜜臀AV一区二区| 日本巨大的奶头在线观看| 无码人妻毛片丰滿熟婦区毛片色欲| 亚洲成AV人片无码不卡| 中文字幕日韩人妻在线乱码| 成人无码H动漫在线播放| 国产无套码AⅤ在线观看| 久久久久亚洲AV片无码V| 人妻AV一区二区| 香蕉久久夜色精品升级完成| 用嘴巴吃鸡的好处| 成人动漫在线观看| 国内精品久久久久影院中文字幕| 免费播放AV网站的地址| 色狠狠久久AV北条麻妃| 亚洲国产精品久久一线不卡| 2020国产精品永久在线| 国产成人精品日本亚洲第一区 | 国产精品视频一区二区三区四| 久久国产精品99国产精| 青青草国产成人99久久| 香蕉免费一区二区三区在| 在线天堂中文最新版| 懂色av一区二区三区蜜臀| 交换朋友夫妻客厅互换4韩国| 欧美黑人乱猛交xX 乂500| 无人高清影视在线观看| 中文成人无字幕乱码精品区 | 亚洲AV中文无码乱人伦在线R▽| 51CG今日吃瓜热门大瓜| 国产精品露脸视频观看| 麻花豆传媒剧国产MV的特点| 未满十八岁可以去日本留学吗| 亚洲综合色一区二区三区| 又粗又大又黄又爽的免费视频| 亚洲AV中文无码字幕色本草| 夜夜未满十八勿进的爽爽影院| 99RE热这里只有精品视频| 国产精品 高清 尿 小便 嘘嘘| 久久午夜私人影院| 天天摸日日摸狠狠添| 曰韩少妇内射免费播放 | 前夫6天要了我25次| 亚洲AV极品熟妇一品二品三品 | 国内精品自产拍在线观看| 欧美男男作爱GAYWWW| 亚洲AV怡红院AV男人的天堂| 被滋润的少妇疯狂呻吟| 久久久久成人精品无码中文字幕| 色欲AⅤ亚洲情无码AV蜜桃| 又湿又紧又大又爽又A视频| 国产精品日本一区二区在线播放 | 无码视频一区二区三区| 99精品热6080YY久久| 精品国产国语对白久久免费| 搡搡BB搡搡搡搡BBB| 曰本丰满成熟xxxx精品| 国产欧美久久一区二区| 人妻VA精品VA欧美VA| 亚洲中文字幕无码AV正片| 国产成人无码区免费内射一片色欲| 蜜桃av中文字幕| 亚洲AV综合色区无码二区偷拍| 成人免费视频一区二区| 久久夜色精品国产亚洲AV| 新版孕妇BBWBBW| 成人欧美一区二区三区性视频| 噜噜噜噜噜18禁私人影视| 午夜无码片在线观看影院网址 | 日韩成人无码一区二区三区| 涨乳催乳改造调教公主| 激情97综合亚洲色婷婷五| 特级做A爰片毛片免费看108| WWW一区二区WWW免费| 巨人精品福利官方导航| 亚洲大胸美女被操喷水| 国产99网站免在线观看| 人妻丰滿熟妇αV无码HD| 中文精品久久久久人妻不卡| 精品久久久久成人码免费动漫| 无码视频一区二区三区| 疯狂做受XXXX欧美老人| 无码中文AV波多野结衣| 18大禁漫画吃奶羞羞漫画| 国产美女精品视频线免费播放软件| 国内一区二区三区香蕉AⅤ| 中文字幕乱偷无码动漫AV| 久久亚洲人成网站| 亚洲国产精品久久艾草| 国产精品日本一区二区在线播放| 日韩人妻中文无码一区二区七区| 99久久综合狠狠综合久久AⅤ| 精品久久人人爽天天玩人人妻| 日韩欧美人妻在线| 中文字幕人妻在线中字| 欧美成人看片一区二区三区尤物| 他的白月光H1∨1笔趣阁| 办公室撕开奶罩揉吮奶头H文| 奶头被客人玩的又红又肿| 伊人色综合久久天天| 久久精品国产亚洲AV麻豆图片 | 公主很忙(N)甜烟| 日日摸夜夜爽无码毛片精选| 波多野结衣一区二区三区高清 | 欧美搡BBBBB搡BBBBB| 在线观看亚洲AV电影网站| 精品人人妻人人爽D∨D| 亚洲阿V天堂无码2020| 国产亚洲欧美日韩二三线| 亚洲ⅤA中文字幕无码毛片| 国产精品揄拍100视频| 我和亲妺在浴室作爱H伦| 国产成年女人毛片80S网站| 中文字幕日韩精品有码视频| 国产激情久久久久影院老熟女免费 | 日本高清视频WWW| 亚洲成AV人无码| 好儿子妈妈今后就是你的人| 亚洲AV成人一区二区三区 | 久久精品国产亚洲夜色AV网站| 亚洲日本VA中文字幕久久道具| 久久精品人人槡人妻人人玩| 亚洲日韩欧洲乱码AV夜夜摸 | 公翁的粗大放进我的秘密小说| 日韩精品一区二区三区四区蜜桃| 成人免费无遮挡在线播放| 日韩精品人妻系列无码专区| 丰满的继牳3中文字幕系列| 特级做A爰片毛片免费看108| 国产美女久久精品香蕉69| 亚洲AV成人一区国产精品小说| 狠狠躁夜夜躁人人爽天天天天97 | 亚洲成人AV网址| 久久精品第一国产久精国产宅男6 久久精品第九区免费观看 | 贵阳40多岁熟女高潮呻吟| 无码丰满人妻熟妇区| 国产又爽又黄又舒服又刺激视频 | 久久久亚洲欧洲日产国码ΑV| 一本大道色卡1卡2卡3| 毛片A级毛片免费观看品善网| 9LPORM自拍视频区九色| 日韩精品无码一本二本三本色| 国产成人无码精品午夜福利A | 好了AV四色综合无码久久| 亚洲最新版AV无码中文字幕 | 99国产精品国产精品九九| 日韩欧美一区二区三区免费观看 | 亚洲欧洲自拍拍偷精品网314| 麻豆国产蜜桃臀视频在线观看| YYYY11111少妇无码影院| 色综合久久综合欧美综合网| 国产熟女亚洲精品麻豆| 野花视频大全高清免费| 欧美日韩视频一区二区| 公车掀起老师裙子进入在线| 亚洲AV无码性色AV无码网站| 老师的兔子好多软水好多动漫| H国产小视频福利免费视频| 天堂AV旡码AV毛片毛片免费| 激情综合亚洲色婷婷五月| 中文字幕少妇人妻av护士人妻| 日本亚洲色大成网站www久久| 国产人澡人澡澡澡人碰视频| 亚洲最大成人网站| 国产农村妇女毛片精品久久麻豆| 综合激情丁香久久狠狠| 午夜男女爽爽影院_性夜影院| 精品水蜜桃久久久久久久| AV鲁丝一区鲁丝二区鲁丝四| 熟悉妇人妻av无码毛片| 久久国产一区二区三区| ぱらだいす天堂中文网WWW在线 | 国产成人综合久久精品推最新| 亚洲欧洲国产码专区在线观看| 欧美午夜性春猛交ⅩXXX| 国产乱码1卡二卡3卡四卡| 亚洲日韩中文字幕无码一区| 亚洲男人第一无码AV网站| 亚洲AV少妇熟女猛男| 内射白嫩少妇超碰| 男男gv在线观看| 精品综合久久久久久888蜜芽| 粗大黑人巨茎大战欧美成人免费看|