91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

詳解vue的diff算法原理

發布時間:2020-08-19 18:20:02 來源:腳本之家 閱讀:198 作者:_wind 欄目:web開發

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

先來了解幾個點...

1. 當數據發生變化時,vue是怎么更新節點的?

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

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

diff的過程就是調用名為 patch 的函數,比較新舊節點,一邊比較一邊給 真實的DOM 打補丁。

2. virtual DOM和真實DOM的區別?

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

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

對應的virtual DOM(偽代碼):

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

(溫馨提示: VNodeoldVNode 都是對象,一定要記住)

3. diff的比較方式?

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

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

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

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

詳解vue的diff算法原理

diff流程圖

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

詳解vue的diff算法原理

具體分析

patch

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

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

patch函數接收兩個參數 oldVnodeVnode 分別代表新的節點和之前的舊節點

判斷兩節點是否值得比較,值得比較則執行 patchVnode

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

不值得比較則用 Vnode 替換 oldVnode

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

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

patchVnode

當我們確定兩個節點值得比較之后我們會對兩個節點指定 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)
  }
 }
}

這個函數做了以下事情:

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

其他幾個點都很好理解,我們詳細來講一下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)
 }
}

先說一下這個函數做了什么

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

圖解updateChildren

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

粉紅色的部分為oldCh和vCh

詳解vue的diff算法原理

我們將它們取出來并分別用s和e指針指向它們的頭child和尾child

詳解vue的diff算法原理

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

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

再配個圖

詳解vue的diff算法原理

第一步

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

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

第二步

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

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

第三步

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

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

第四步

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

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

一次模擬完成。

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

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

詳解vue的diff算法原理

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

詳解vue的diff算法原理

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

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

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

總結

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

詳解vue的diff算法原理

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

遵化市| 宁陕县| 德州市| 旌德县| 连城县| 弥渡县| 平舆县| 辽阳市| 南涧| 安宁市| 招远市| 静乐县| 威信县| 肇源县| 西充县| 交城县| 余姚市| 永川市| 凌云县| 澎湖县| 乌兰浩特市| 蕲春县| 丹寨县| 昆明市| 五指山市| 德化县| 拜泉县| 逊克县| 洮南市| 临汾市| 陕西省| 嘉黎县| 临清市| 翼城县| 八宿县| 鄱阳县| 沛县| 嘉峪关市| 梧州市| 铁岭市| 会泽县|