亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解vue的diff算法原理

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

我的目標(biāo)是寫一個(gè)非常詳細(xì)的關(guān)于diff的干貨,所以本文有點(diǎn)長。也會(huì)用到大量的圖片以及代碼舉例,目的讓看這篇文章的朋友一定弄明白diff的邊邊角角。

先來了解幾個(gè)點(diǎn)...

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

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

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

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

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

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

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

對(duì)應(yīng)的virtual DOM(偽代碼):

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

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

3. diff的比較方式?

在采取diff算法比較新舊節(jié)點(diǎn)的時(shí)候,比較只會(huì)在同層級(jí)進(jìn)行, 不會(huì)跨層級(jí)比較。

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

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

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

diff流程圖

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

具體分析

patch

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

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

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

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

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

不值得比較則用 Vnode 替換 oldVnode

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

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

patchVnode

當(dāng)我們確定兩個(gè)節(jié)點(diǎn)值得比較之后我們會(huì)對(duì)兩個(gè)節(jié)點(diǎn)指定 patchVnode 方法。那么這個(gè)方法做了什么呢?

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)
  }
 }
}

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

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

其他幾個(gè)點(diǎn)都很好理解,我們?cè)敿?xì)來講一下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) { // 對(duì)于vnode.key的比較,會(huì)把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時(shí)的比較
   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)
 }
}

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

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

圖解updateChildren

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

粉紅色的部分為oldCh和vCh

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

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

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

再配個(gè)圖

第一步

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

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

第二步

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

oldSE 匹配,就將原本的b節(jié)點(diǎn)移動(dòng)到最后,因?yàn)?E 是最后一個(gè)節(jié)點(diǎn),他們位置要一致,這就是上面說的: 當(dāng)其中兩個(gè)能匹配上那么真實(shí)dom中的相應(yīng)節(jié)點(diǎn)會(huì)移到Vnode相應(yīng)的位置 ,此時(shí)dom的位置為:a d b

第三步

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

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

第四步

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

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

一次模擬完成。

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

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

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

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

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

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

總結(jié)

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

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

相關(guān)文章

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

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

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

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

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

    詳解jenkins自動(dòng)化部署vue

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

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

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

    Vue3中級(jí)指南之如何在vite中使用svg圖標(biāo)詳解

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

    vue封裝swiper代碼實(shí)例解析

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

    vue的$http的get請(qǐng)求要加上params操作

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

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

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

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

    在Vue進(jìn)行前端開發(fā)中,條件判斷主要用于根據(jù)不同的條件來決定顯示或隱藏,或者進(jìn)行視圖之間的切換,這篇文章主要給大家介紹了關(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中定義變量的變化詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2019-11-11

最新評(píng)論