最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解vue的diff算法原理

 更新時間:2018年05月20日 16:35:16   作者:_wind  
這篇文章主要介紹了詳解vue的diff算法原理,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

我的目標是寫一個非常詳細的關于diff的干貨,所以本文有點長。也會用到大量的圖片以及代碼舉例,目的讓看這篇文章的朋友一定弄明白diff的邊邊角角。

先來了解幾個點...

1. 當數(shù)據(jù)發(fā)生變化時,vue是怎么更新節(jié)點的?

要知道渲染真實DOM的開銷是很大的,比如有時候我們修改了某個數(shù)據(jù),如果直接渲染到真實dom上會引起整個dom樹的重繪和重排,有沒有可能我們只更新我們修改的那一小塊dom而不要更新整個dom呢?diff算法能夠幫助我們。

我們先根據(jù)真實DOM生成一顆 virtual DOM ,當 virtual DOM 某個節(jié)點的數(shù)據(jù)改變后會生成一個新的 Vnode ,然后 VnodeoldVnode 作對比,發(fā)現(xiàn)有不一樣的地方就直接修改在真實的DOM上,然后使 oldVnode 的值為 Vnode

diff的過程就是調(diào)用名為 patch 的函數(shù),比較新舊節(jié)點,一邊比較一邊給 真實的DOM 打補丁。

2. virtual DOM和真實DOM的區(qū)別?

virtual DOM是將真實的DOM的數(shù)據(jù)抽取出來,以對象的形式模擬樹形結構。比如dom是這樣的:

<div>
 <p>123</p>
</div>

對應的virtual DOM(偽代碼):

var Vnode = {
 tag: 'div',
 children: [
  { tag: 'p', text: '123' }
 ]
};

(溫馨提示: VNodeoldVNode 都是對象,一定要記?。?/p>

3. diff的比較方式?

在采取diff算法比較新舊節(jié)點的時候,比較只會在同層級進行, 不會跨層級比較。

<div>
 <p>123</p>
</div>

<div>
 <span>456</span>
</div>

上面的代碼會分別比較同一層的兩個div以及第二層的p和span,但是不會拿div和span作比較。在別處看到的一張很形象的圖:

diff流程圖

當數(shù)據(jù)發(fā)生改變時,set方法會讓調(diào)用 Dep.notify 通知所有訂閱者Watcher,訂閱者就會調(diào)用 patch 給真實的DOM打補丁,更新相應的視圖。

具體分析

patch

來看看 patch 是怎么打補丁的(代碼只保留核心部分)

function patch (oldVnode, vnode) {
 // some code
 if (sameVnode(oldVnode, vnode)) {
  patchVnode(oldVnode, vnode)
 } else {
  const oEl = oldVnode.el // 當前oldVnode對應的真實元素節(jié)點
  let parentEle = api.parentNode(oEl) // 父元素
  createEle(vnode) // 根據(jù)Vnode生成新元素
  if (parentEle !== null) {
   api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl)) // 將新元素添加進父元素
   api.removeChild(parentEle, oldVnode.el) // 移除以前的舊元素節(jié)點
   oldVnode = null
  }
 }
 // some code 
 return vnode
}

patch函數(shù)接收兩個參數(shù) oldVnodeVnode 分別代表新的節(jié)點和之前的舊節(jié)點

判斷兩節(jié)點是否值得比較,值得比較則執(zhí)行 patchVnode

function sameVnode (a, b) {
 return (
 a.key === b.key && // key值
 a.tag === b.tag && // 標簽名
 a.isComment === b.isComment && // 是否為注釋節(jié)點
 // 是否都定義了data,data包含一些具體信息,例如onclick , style
 isDef(a.data) === isDef(b.data) && 
 sameInputType(a, b) // 當標簽是<input>的時候,type必須相同
 )
}

不值得比較則用 Vnode 替換 oldVnode

如果兩個節(jié)點都是一樣的,那么就深入檢查他們的子節(jié)點。如果兩個節(jié)點不一樣那就說明 Vnode 完全被改變了,就可以直接替換 oldVnode 。

雖然這兩個節(jié)點不一樣但是他們的子節(jié)點一樣怎么辦?別忘了,diff可是逐層比較的,如果第一層不一樣那么就不會繼續(xù)深入比較第二層了。(我在想這算是一個缺點嗎?相同子節(jié)點不能重復利用了...)

patchVnode

當我們確定兩個節(jié)點值得比較之后我們會對兩個節(jié)點指定 patchVnode 方法。那么這個方法做了什么呢?

patchVnode (oldVnode, vnode) {
 const el = vnode.el = oldVnode.el
 let i, oldCh = oldVnode.children, ch = vnode.children
 if (oldVnode === vnode) return
 if (oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text) {
  api.setTextContent(el, vnode.text)
 }else {
  updateEle(el, vnode, oldVnode)
  if (oldCh && ch && oldCh !== ch) {
   updateChildren(el, oldCh, ch)
  }else if (ch){
   createEle(vnode) //create el's children dom
  }else if (oldCh){
   api.removeChildren(el)
  }
 }
}

這個函數(shù)做了以下事情:

  1. 找到對應的真實dom,稱為 el
  2. 判斷 VnodeoldVnode 是否指向同一個對象,
  3. 如果是,那么直接 return 如果他們都有文本節(jié)點并且不相等,那么將 el 的文本節(jié)點設置為 Vnode 的文本節(jié)點。
  4. 如果 oldVnode 有子節(jié)點而 Vnode 沒有,則刪除 el 的子節(jié)點
  5. 如果 oldVnode 沒有子節(jié)點而 Vnode 有,則將 Vnode 的子節(jié)點真實化之后添加到 el 如果兩者都有子節(jié)點,則執(zhí)行 updateChildren 函數(shù)比較子節(jié)點,這一步很重要

其他幾個點都很好理解,我們詳細來講一下updateChildren

updateChildren

代碼量很大,不方便一行一行的講解,所以下面結合一些示例圖來描述一下。

updateChildren (parentElm, oldCh, newCh) {
 let oldStartIdx = 0, 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
 let idxInOld
 let elmToMove
 let before
 while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
  if (oldStartVnode == null) { // 對于vnode.key的比較,會把oldVnode = null
   oldStartVnode = oldCh[++oldStartIdx] 
  }else if (oldEndVnode == null) {
   oldEndVnode = oldCh[--oldEndIdx]
  }else if (newStartVnode == null) {
   newStartVnode = newCh[++newStartIdx]
  }else if (newEndVnode == null) {
   newEndVnode = newCh[--newEndIdx]
  }else if (sameVnode(oldStartVnode, newStartVnode)) {
   patchVnode(oldStartVnode, newStartVnode)
   oldStartVnode = oldCh[++oldStartIdx]
   newStartVnode = newCh[++newStartIdx]
  }else if (sameVnode(oldEndVnode, newEndVnode)) {
   patchVnode(oldEndVnode, newEndVnode)
   oldEndVnode = oldCh[--oldEndIdx]
   newEndVnode = newCh[--newEndIdx]
  }else if (sameVnode(oldStartVnode, newEndVnode)) {
   patchVnode(oldStartVnode, newEndVnode)
   api.insertBefore(parentElm, oldStartVnode.el, api.nextSibling(oldEndVnode.el))
   oldStartVnode = oldCh[++oldStartIdx]
   newEndVnode = newCh[--newEndIdx]
  }else if (sameVnode(oldEndVnode, newStartVnode)) {
   patchVnode(oldEndVnode, newStartVnode)
   api.insertBefore(parentElm, oldEndVnode.el, oldStartVnode.el)
   oldEndVnode = oldCh[--oldEndIdx]
   newStartVnode = newCh[++newStartIdx]
  }else {
   // 使用key時的比較
   if (oldKeyToIdx === undefined) {
    oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // 有key生成index表
   }
   idxInOld = oldKeyToIdx[newStartVnode.key]
   if (!idxInOld) {
    api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)
    newStartVnode = newCh[++newStartIdx]
   }
   else {
    elmToMove = oldCh[idxInOld]
    if (elmToMove.sel !== newStartVnode.sel) {
     api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)
    }else {
     patchVnode(elmToMove, newStartVnode)
     oldCh[idxInOld] = null
     api.insertBefore(parentElm, elmToMove.el, oldStartVnode.el)
    }
    newStartVnode = newCh[++newStartIdx]
   }
  }
 }
 if (oldStartIdx > oldEndIdx) {
  before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].el
  addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx)
 }else if (newStartIdx > newEndIdx) {
  removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
 }
}

先說一下這個函數(shù)做了什么

  1. Vnode 的子節(jié)點 VcholdVnode 的子節(jié)點 oldCh 提取出來
  2. oldChvCh 各有兩個頭尾的變量 StartIdxEndIdx ,它們的2個變量相互比較,一共有4種比較方式。如果4種比較都沒匹配,如果設置了 key ,就會用 key 進行比較,在比較的過程中,變量會往中間靠,一旦 StartIdx>EndIdx 表明 oldChvCh 至少有一個已經(jīng)遍歷完了,就會結束比較。

圖解updateChildren

終于來到了這一部分,上面的總結相信很多人也看得一臉懵逼,下面我們好好說道說道。(這都是我自己畫的,求推薦好用的畫圖工具...)

粉紅色的部分為oldCh和vCh

我們將它們?nèi)〕鰜聿⒎謩e用s和e指針指向它們的頭child和尾child

現(xiàn)在分別對 oldS、oldE、S、E 兩兩做 sameVnode 比較,有四種比較方式,當其中兩個能匹配上那么真實dom中的相應節(jié)點會移到Vnode相應的位置,這句話有點繞,打個比方

  1. 如果是oldS和E匹配上了,那么真實dom中的第一個節(jié)點會移到最后
  2. 如果是oldE和S匹配上了,那么真實dom中的最后一個節(jié)點會移到最前,匹配上的兩個指針向中間移動
  3. 如果四種匹配沒有一對是成功的,那么遍歷 oldChildS 挨個和他們匹配,匹配成功就在真實dom中將成功的節(jié)點移到最前面,如果依舊沒有成功的,那么將 S對應的節(jié)點 插入到dom中對應的 oldS 位置, oldSS 指針向中間移動。

再配個圖

第一步

oldS = a, oldE = d;
S = a, E = b;

oldSS 匹配,則將dom中的a節(jié)點放到第一個,已經(jīng)是第一個了就不管了,此時dom的位置為:a b d

第二步

oldS = b, oldE = d;
S = c, E = b;

oldSE 匹配,就將原本的b節(jié)點移動到最后,因為 E 是最后一個節(jié)點,他們位置要一致,這就是上面說的: 當其中兩個能匹配上那么真實dom中的相應節(jié)點會移到Vnode相應的位置 ,此時dom的位置為:a d b

第三步

oldS = d, oldE = d;
S = c, E = d;

oldEE 匹配,位置不變此時dom的位置為:a d b

第四步

oldS++;
oldE--;
oldS > oldE;

遍歷結束,說明 oldCh 先遍歷完。就將剩余的 vCh 節(jié)點根據(jù)自己的的index插入到真實dom中去,此時dom位置為:a c d b

一次模擬完成。

這個匹配過程的結束有兩個條件:

oldS > oldE 表示 oldCh 先遍歷完,那么就將多余的 vCh 根據(jù)index添加到dom中去(如上圖) S > E 表示vCh先遍歷完,那么就在真實dom中將區(qū)間為 [oldS, oldE] 的多余節(jié)點刪掉

下面再舉一個例子,可以像上面那樣自己試著模擬一下

當這些節(jié)點 sameVnode 成功后就會緊接著執(zhí)行 patchVnode 了,可以看一下上面的代碼

if (sameVnode(oldStartVnode, newStartVnode)) {
 patchVnode(oldStartVnode, newStartVnode)
}

就這樣層層遞歸下去,直到將oldVnode和Vnode中的所有子節(jié)點比對完。也將dom的所有補丁都打好啦。那么現(xiàn)在再回過去看updateChildren的代碼會不會容易很多呢?

總結

以上為diff算法的全部過程,放上一張文章開始就發(fā)過的總結圖,可以試試看著這張圖回憶一下diff的過程。

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • Vue組件間傳遞數(shù)據(jù)的多種方法

    Vue組件間傳遞數(shù)據(jù)的多種方法

    在實際開發(fā)中,Vue組件之間的數(shù)據(jù)傳遞是最常見的需求,由于組件的作用域相互獨立,如何在父子、兄弟和跨級組件間傳遞數(shù)據(jù)就顯得尤為重要,本文將詳細介紹多種Vue組件間傳遞數(shù)據(jù)的方,需要的朋友可以參考下
    2025-03-03
  • vue3.0公共組件自動導入的方法實例

    vue3.0公共組件自動導入的方法實例

    這篇文章主要給大家介紹了關于vue3.0公共組件自動導入的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-04-04
  • vue3中使用editor.js的詳細步驟記錄

    vue3中使用editor.js的詳細步驟記錄

    富文本編輯器作為直接與用戶交互的內(nèi)容輸入生產(chǎn)工具,對大家的項目來說非常重要,下面這篇文章主要給大家介紹了關于vue3中使用editor.js的詳細步驟,需要的朋友可以參考下
    2024-01-01
  • Element-Plus之el-col與el-row快速布局

    Element-Plus之el-col與el-row快速布局

    el-col是el-row的子元素,下面這篇文章主要給大家介紹了關于Element-Plus之el-col與el-row快速布局的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-09-09
  • 淺談關于iview表單驗證的問題

    淺談關于iview表單驗證的問題

    這篇文章主要介紹了淺談關于iview表單驗證的問題,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-09-09
  • 解決vue項目報錯webpackJsonp is not defined問題

    解決vue項目報錯webpackJsonp is not defined問題

    下面小編就為大家分享一篇解決vue項目報錯webpackJsonp is not defined問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-03-03
  • vue2 el-table行懸停時彈出提示信息el-popover的實現(xiàn)

    vue2 el-table行懸停時彈出提示信息el-popover的實現(xiàn)

    本文主要介紹了vue2 el-table行懸停時彈出提示信息el-popover的實現(xiàn),用到了cell-mouse-enter、cell-mouse-leave兩個事件,具有一定的參考價值,感興趣的可以了解一下
    2024-01-01
  • vue three.js創(chuàng)建地面并設置陰影實現(xiàn)示例

    vue three.js創(chuàng)建地面并設置陰影實現(xiàn)示例

    這篇文章主要為大家介紹了vue three.js創(chuàng)建地面并設置陰影實現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • Vue之表單事件數(shù)據(jù)綁定詳解

    Vue之表單事件數(shù)據(jù)綁定詳解

    這篇文章主要為大家介紹了Vue之表單事件的數(shù)據(jù)綁定,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助,希望能夠給你帶來幫助
    2021-11-11
  • Vue.js實現(xiàn)文件上傳和進度條顯示功能

    Vue.js實現(xiàn)文件上傳和進度條顯示功能

    在現(xiàn)代Web開發(fā)中,文件上傳是一個常見而重要的需求,無論是在用戶上傳頭像、文檔或者其他類型的文件時,良好的用戶體驗都是至關重要的,在這篇博客中,我們將介紹如何使用Vue.js來實現(xiàn)文件上傳功能,同時顯示上傳進度條,需要的朋友可以參考下
    2024-11-11

最新評論

刚察县| 呼伦贝尔市| 盐源县| 平邑县| 剑川县| 固原市| 太仆寺旗| 乐陵市| 临湘市| 芦溪县| 莱西市| 绥化市| 车致| 夏河县| 门头沟区| 铜川市| 大化| 兴安县| 大丰市| 左权县| 瑞丽市| 高邮市| 赞皇县| 延川县| 上思县| 敖汉旗| 堆龙德庆县| 西畴县| 栖霞市| 新建县| 周至县| 文登市| 永年县| 巴塘县| 邓州市| 兰坪| 自贡市| 高尔夫| 图们市| 台东县| 胶州市|