欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解vue的diff算法原理

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

我的目標是寫一個非常詳細的關(guā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ù)抽取出來,以對象的形式模擬樹形結(jié)構(gòu)。比如dom是這樣的:

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

對應(yīng)的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打補丁,更新相應(yīng)的視圖。

具體分析

patch

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

function patch (oldVnode, vnode) {
 // some code
 if (sameVnode(oldVnode, vnode)) {
  patchVnode(oldVnode, vnode)
 } else {
  const oEl = oldVnode.el // 當前oldVnode對應(yīng)的真實元素節(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é)點不能重復(fù)利用了...)

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

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

updateChildren

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

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種比較都沒匹配,如果設(shè)置了 key ,就會用 key 進行比較,在比較的過程中,變量會往中間靠,一旦 StartIdx>EndIdx 表明 oldChvCh 至少有一個已經(jīng)遍歷完了,就會結(jié)束比較。

圖解updateChildren

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

粉紅色的部分為oldCh和vCh

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

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

  1. 如果是oldS和E匹配上了,那么真實dom中的第一個節(jié)點會移到最后
  2. 如果是oldE和S匹配上了,那么真實dom中的最后一個節(jié)點會移到最前,匹配上的兩個指針向中間移動
  3. 如果四種匹配沒有一對是成功的,那么遍歷 oldChild , S 挨個和他們匹配,匹配成功就在真實dom中將成功的節(jié)點移到最前面,如果依舊沒有成功的,那么將 S對應(yīng)的節(jié)點 插入到dom中對應(yīng)的 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中的相應(yīng)節(jié)點會移到Vnode相應(yīng)的位置 ,此時dom的位置為:a d b

第三步

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

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

第四步

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

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

一次模擬完成。

這個匹配過程的結(jié)束有兩個條件:

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的代碼會不會容易很多呢?

總結(jié)

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

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

相關(guān)文章

  • vue組件中的數(shù)據(jù)傳遞方法

    vue組件中的數(shù)據(jù)傳遞方法

    這篇文章主要介紹了vue組件中的數(shù)據(jù)傳遞方法,非常不錯,具有一定的參考借鑒價值,需要的朋友參考下吧
    2018-05-05
  • vue3如何實現(xiàn)PDF文件在線預(yù)覽功能

    vue3如何實現(xiàn)PDF文件在線預(yù)覽功能

    PDF文件在線預(yù)覽的功能相信大家都是有遇到過的,下面這篇文章主要給大家介紹了關(guān)于vue3如何實現(xiàn)PDF文件在線預(yù)覽功能的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-06-06
  • 詳解jenkins自動化部署vue

    詳解jenkins自動化部署vue

    這篇文章主要介紹了jenkins自動化部署vue,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05
  • 前端部署踩坑實戰(zhàn)記錄(部署后404、頁面空白)

    前端部署踩坑實戰(zhàn)記錄(部署后404、頁面空白)

    Vue項目打包部署Nginx服務(wù)器后,刷新頁面后出現(xiàn)404的問題,下面這篇文章主要給大家介紹了關(guān)于前端部署踩坑的實戰(zhàn)記錄,文中包括部署后404、頁面空白等問題的解決辦法,需要的朋友可以參考下
    2024-09-09
  • Vue3中級指南之如何在vite中使用svg圖標詳解

    Vue3中級指南之如何在vite中使用svg圖標詳解

    在以webpack為構(gòu)建工具的開發(fā)環(huán)境中我們可以很方便的實現(xiàn)SVG圖標的組件化,下面這篇文章主要給大家介紹了關(guān)于Vue3中級指南之如何在vite中使用svg圖標的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-04-04
  • vue封裝swiper代碼實例解析

    vue封裝swiper代碼實例解析

    這篇文章主要介紹了vue封裝swiper代碼實例解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-10-10
  • vue的$http的get請求要加上params操作

    vue的$http的get請求要加上params操作

    這篇文章主要介紹了vue的$http的get請求要加上params操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • 在Vue3中使用vue3-print-nb實現(xiàn)前端打印功能

    在Vue3中使用vue3-print-nb實現(xiàn)前端打印功能

    在前端開發(fā)中,經(jīng)常需要打印頁面的特定部分,比如客戶列表或商品詳情頁,要快速實現(xiàn)這些功能,可以使用 vue3-print-nb 插件,本文就給大家介紹了如何在 Vue 3 中使用 vue3-print-nb 實現(xiàn)靈活的前端打印,需要的朋友可以參考下
    2024-06-06
  • Vue條件判斷之循環(huán)舉例詳解

    Vue條件判斷之循環(huán)舉例詳解

    在Vue進行前端開發(fā)中,條件判斷主要用于根據(jù)不同的條件來決定顯示或隱藏,或者進行視圖之間的切換,這篇文章主要給大家介紹了關(guān)于Vue條件判斷之循環(huán)舉例詳解的相關(guān)資料,需要的朋友可以參考下
    2024-07-07
  • vue之組件內(nèi)監(jiān)控$store中定義變量的變化詳解

    vue之組件內(nèi)監(jiān)控$store中定義變量的變化詳解

    今天小編就為大家分享一篇vue之組件內(nèi)監(jiān)控$store中定義變量的變化詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-11-11

最新評論