自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一文帶你徹底搞定Diff算法

云計(jì)算 虛擬化 算法
Diff算法實(shí)現(xiàn)的是最小量更新虛擬DOM。這句話雖然簡短,但是涉及到了兩個(gè)核心要素:虛擬DOM、最小量更新。虛擬DOM指的就是將真實(shí)的DOM樹構(gòu)造為js對(duì)象的形式,從而解決瀏覽器操作真實(shí)DOM的性能問題。

[[420540]]

一、基礎(chǔ)

Diff算法實(shí)現(xiàn)的是最小量更新虛擬DOM。這句話雖然簡短,但是涉及到了兩個(gè)核心要素:虛擬DOM、最小量更新。

1.虛擬DOM

虛擬DOM指的就是將真實(shí)的DOM樹構(gòu)造為js對(duì)象的形式,從而解決瀏覽器操作真實(shí)DOM的性能問題。

例如:如下DOM與虛擬DOM之間的映射關(guān)系

2.最小量更新

Diff的用途就是在新老虛擬DOM之間找到最小更新的部分,從而將該部分對(duì)應(yīng)的DOM進(jìn)行更新。

二、整個(gè)流程

Diff算法真的很美,整個(gè)流程如下圖所示:

  1. 首先比較一下新舊節(jié)點(diǎn)是不是同一個(gè)節(jié)點(diǎn)(可通過比較sel(選擇器)和key(唯一標(biāo)識(shí))值是不是相同),不是同一個(gè)節(jié)點(diǎn)則進(jìn)行暴力刪除(注:先以舊節(jié)點(diǎn)為基準(zhǔn)插入新節(jié)點(diǎn),然后再刪除舊節(jié)點(diǎn))。
  2. 若是同一個(gè)節(jié)點(diǎn)則需要進(jìn)一步比較

完全相同,不做處理

新節(jié)點(diǎn)內(nèi)容為文本,直接替換完事

新節(jié)點(diǎn)有子節(jié)點(diǎn),這個(gè)時(shí)候就要仔細(xì)考慮一下了:若老節(jié)點(diǎn)沒有子元素,則直接清空老節(jié)點(diǎn),將新節(jié)點(diǎn)的子元素插入即可;若老節(jié)點(diǎn)有子元素則就需要按照上述的更新策略老搞定了(記住更新策略,又可以吹好幾年了,666666)。

三、實(shí)戰(zhàn)

光說不練假把式,下面直接輸出diff算法的核心內(nèi)容。

3.1 patch函數(shù)

Diff算法的入口函數(shù),主要判斷新舊節(jié)點(diǎn)是不是同一個(gè)節(jié)點(diǎn),然后交由不同的邏輯進(jìn)行處理。

  1. export default function patch(oldVnode, newVnode) { 
  2.     // 判斷傳入的第一個(gè)參數(shù),是DOM節(jié)點(diǎn)還是虛擬節(jié)點(diǎn) 
  3.     if (oldVnode.sel === '' || oldVnode.sel === undefined) { 
  4.         // 傳入的第一個(gè)參數(shù)是DOM節(jié)點(diǎn),此時(shí)要包裝成虛擬節(jié)點(diǎn) 
  5.         oldVnode = vnode(oldVnode.tagName.toLowerCase(), {}, [], undefined, oldVnode); 
  6.     } 
  7.  
  8.     // 判斷oldVnode和newVnode是不是同一個(gè)節(jié)點(diǎn) 
  9.     if (oldVnode.key === newVnode.key && oldVnode.sel === newVnode.sel) { 
  10.         //是同一個(gè)節(jié)點(diǎn),則進(jìn)行精細(xì)化比較 
  11.         patchVnode(oldVnode, newVnode); 
  12.     } 
  13.     else { 
  14.         // 不是同一個(gè)節(jié)點(diǎn),暴力插入新的,刪除舊的 
  15.         let newVnodeElm = createElement(newVnode); 
  16.  
  17.         // 將新節(jié)點(diǎn)插入到老節(jié)點(diǎn)之前 
  18.         if (oldVnode.elm.parentNode && newVnodeElm) { 
  19.             oldVnode.elm.parentNode.insertBefore(newVnodeElm, oldVnode.elm); 
  20.         } 
  21.         // 刪除老節(jié)點(diǎn) 
  22.         oldVnode.elm.parentNode.removeChild(oldVnode.elm); 
  23.     } 

3.2 patchVnode函數(shù)

該函數(shù)主要負(fù)責(zé)精細(xì)化比較,通過按照上述流程圖中的邏輯依次判斷屬于哪一個(gè)分支,從而采取不同的處理邏輯。(思路清晰,算法太牛了)

  1. export default function patchVnode(oldVnode, newVnode) { 
  2.     // 判斷新舊vnode是否是同一個(gè)對(duì)象 
  3.     if (oldVnode === newVnode) { 
  4.         return
  5.     } 
  6.     // 判斷vnode有沒有text屬性 
  7.     if (newVnode.text !== undefined && (newVnode.children === undefined || newVnode.children.length === 0)) { 
  8.         console.log('新vnode有text屬性'); 
  9.         if (newVnode.text !== oldVnode.text) { 
  10.             oldVnode.elm.innerText = newVnode.text; 
  11.         } 
  12.     } 
  13.     else { 
  14.         // 新vnode沒有text屬性,有children 
  15.         console.log('新vnode沒有text屬性'); 
  16.         // 判斷老的有沒有children 
  17.         if (oldVnode.children !== undefined && oldVnode.children.length > 0) { 
  18.             // 老的有children,新的也有children 
  19.             updateChildren(oldVnode.elm, oldVnode.children, newVnode.children); 
  20.         } 
  21.         else { 
  22.             // 老的沒有children,新的有children 
  23.             // 清空老的節(jié)點(diǎn)的內(nèi)容 
  24.             oldVnode.elm.innerHTML = ''
  25.             // 遍歷新的vnode的子節(jié)點(diǎn),創(chuàng)建DOM,上樹 
  26.             for (let i = 0; i < newVnode.children.length; i++) { 
  27.                 let dom = createElement(newVnode.children[i]); 
  28.                 oldVnode.elm.appendChild(dom); 
  29.             } 
  30.         } 
  31.     } 

3.3 updateChildren函數(shù)

核心函數(shù),主要負(fù)責(zé)舊虛擬節(jié)點(diǎn)和新虛擬節(jié)點(diǎn)均存在子元素的情況,按照比較策略依次進(jìn)行比較,最終找出子元素中變化的部分,實(shí)現(xiàn)最小更新。對(duì)于該部分,涉及到一些指針,如下所示:

  1. 舊前指的就是更新前虛擬DOM中的頭部指針
  2. 舊后指的就是更新前虛擬DOM中的尾部指針
  3. 新前指的就是更新后虛擬DOM中的頭部指針
  4. 新后指的就是更新后虛擬DOM中的尾部指針

按照上述的更新策略,上述舊虛擬DOM更新為新虛擬DOM的流程為:

  1. 命中“新后舊前”策略,然后將信后對(duì)應(yīng)的DOM節(jié)點(diǎn)(也就是節(jié)點(diǎn)1)移動(dòng)到舊后節(jié)點(diǎn)(節(jié)點(diǎn)3)后面,然后舊前節(jié)點(diǎn)指針下移,新后節(jié)點(diǎn)指針上移。
  2. 仍然命中“新后舊前”策略,做相同的操作,將節(jié)點(diǎn)2移動(dòng)到舊后節(jié)點(diǎn)(節(jié)點(diǎn)3)后面,下移舊前節(jié)點(diǎn),上移新后節(jié)點(diǎn)。
  3. 命中“新前舊前”策略,DOM節(jié)點(diǎn)不變,舊前和新前節(jié)點(diǎn)均下移。
  4. 跳出循環(huán),移動(dòng)結(jié)束。
  1. export default function updateChildren(parentElm, oldCh, newCh) { 
  2.     // 舊前 
  3.     let oldStartIdx = 0; 
  4.     // 新前 
  5.     let newStartIdx = 0; 
  6.     // 舊后 
  7.     let oldEndIdx = oldCh.length - 1; 
  8.     // 新后 
  9.     let newEndIdx = newCh.length - 1; 
  10.     // 舊前節(jié)點(diǎn) 
  11.     let oldStartVnode = oldCh[0]; 
  12.     // 舊后節(jié)點(diǎn) 
  13.     let oldEndVnode = oldCh[oldEndIdx]; 
  14.     // 新前節(jié)點(diǎn) 
  15.     let newStartVnode = newCh[0]; 
  16.     // 新后節(jié)點(diǎn) 
  17.     let newEndVnode = newCh[newEndIdx]; 
  18.  
  19.     let keyMap = null
  20.  
  21.     while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { 
  22.         // 略過已經(jīng)加undefined標(biāo)記的內(nèi)容 
  23.         if (oldStartVnode == null || oldCh[oldStartIdx] === undefined) { 
  24.             oldStartVnode = oldCh[++oldStartIdx]; 
  25.         } 
  26.         else if (oldEndVnode == null || oldCh[oldEndIdx] === undefined) { 
  27.             oldEndVnode = oldCh[--oldEndIdx]; 
  28.         } 
  29.         else if (newStartVnode == null || newCh[newStartIdx] === undefined) { 
  30.             newStartVnode = newCh[++newStartIdx]; 
  31.         } 
  32.         else if (newEndVnode == null || newCh[newEndIdx] === undefined) { 
  33.             newEndVnode = newCh[--newEndIdx]; 
  34.         } 
  35.         else if (checkSameVnode(oldStartVnode, newStartVnode)) { 
  36.             // 新前與舊前 
  37.             console.log('新前與舊前命中'); 
  38.             patchVnode(oldStartVnode, newStartVnode); 
  39.             oldStartVnode = oldCh[++oldStartIdx]; 
  40.             newStartVnode = newCh[++newStartIdx]; 
  41.         } 
  42.         else if (checkSameVnode(oldEndVnode, newEndVnode)) { 
  43.             // 新后和舊后 
  44.             console.log('新后和舊后命中'); 
  45.             patchVnode(oldEndVnode, newEndVnode); 
  46.             oldEndVnode = oldCh[--oldEndIdx]; 
  47.             newEndVnode = newCh[--newEndVnode]; 
  48.         } 
  49.         else if (checkSameVnode(oldStartVnode, newEndVnode)) { 
  50.             console.log('新后和舊前命中'); 
  51.             patchVnode(oldStartVnode, newEndVnode); 
  52.             // 當(dāng)新后與舊前命中的時(shí)候,此時(shí)要移動(dòng)節(jié)點(diǎn),移動(dòng)新后指向的這個(gè)節(jié)點(diǎn)到老節(jié)點(diǎn)舊后的后面 
  53.             parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling); 
  54.             oldStartVnode = oldCh[++oldStartIdx]; 
  55.             newEndVnode = newCh[--newEndIdx]; 
  56.         } 
  57.         else if (checkSameVnode(oldEndVnode, newStartVnode)) { 
  58.             // 新前和舊后 
  59.             console.log('新前和舊后命中'); 
  60.             patchVnode(oldEndVnode, newStartVnode); 
  61.             // 當(dāng)新前和舊后命中的時(shí)候,此時(shí)要移動(dòng)節(jié)點(diǎn),移動(dòng)新前指向的這個(gè)節(jié)點(diǎn)到老節(jié)點(diǎn)舊前的前面 
  62.             parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm); 
  63.             oldEndVnode = oldCh[--oldEndIdx]; 
  64.             newStartVnode = newCh[++newStartIdx]; 
  65.         } 
  66.         else { 
  67.             // 四種都沒有命中 
  68.             // 制作keyMap一個(gè)映射對(duì)象,這樣就不用每次都遍歷老對(duì)象了 
  69.             if (!keyMap) { 
  70.                 keyMap = {}; 
  71.                 for (let i = oldStartIdx; i <= oldEndIdx; i++) { 
  72.                     const key = oldCh[i].key
  73.                     if (key !== undefined) { 
  74.                         keyMap[key] = i; 
  75.                     } 
  76.                 } 
  77.             } 
  78.             // 尋找當(dāng)前這項(xiàng)(newStartIdx)在keyMap中的映射的位置序號(hào) 
  79.             const idxInOld = keyMap[newStartVnode.key]; 
  80.             if (idxInOld === undefined) { 
  81.                 // 如果idxInOld是undefined表示踏實(shí)全新的項(xiàng),此時(shí)會(huì)將該項(xiàng)創(chuàng)建為DOM節(jié)點(diǎn)并插入到舊前之前 
  82.                 parentElm.insertBefore(createElement(newStartVnode), oldStartVnode.elm); 
  83.             } 
  84.             else { 
  85.                 // 如果不是undefined,則不是全新的項(xiàng),則需要移動(dòng) 
  86.                 const elmToMove = oldCh[idxInOld]; 
  87.                 patchVnode(elmToMove, newStartVnode); 
  88.                 // 把這項(xiàng)設(shè)置為undefined,表示已經(jīng)處理完這項(xiàng)了 
  89.                 oldCh[idxInOld] = undefined; 
  90.                 // 移動(dòng) 
  91.                 parentElm.insertBefore(elmToMove.elm, oldStartVnode.elm); 
  92.             } 
  93.             // 指針下移,只移動(dòng)新的頭 
  94.             newStartVnode = newCh[++newStartIdx]; 
  95.         } 
  96.     } 
  97.  
  98.     // 循環(huán)結(jié)束后,處理未處理的項(xiàng) 
  99.     if (newStartIdx <= newEndIdx) { 
  100.         console.log('new還有剩余節(jié)點(diǎn)沒有處理,要加項(xiàng),把所有剩余的節(jié)點(diǎn)插入到oldStartIdx之前'); 
  101.         // 遍歷新的newCh,添加到老的沒有處理的之前 
  102.         for (let i = newStartIdx; i <= newEndIdx; i++) { 
  103.             // insertBefore方法可以自動(dòng)識(shí)別null,如果是null就會(huì)自動(dòng)排到隊(duì)尾去 
  104.             // newCh[i]現(xiàn)在還沒有真正的DOM,所以要調(diào)用createElement函數(shù)變?yōu)镈OM 
  105.             parentElm.insertBefore(createElement(newCh[i]), oldCh[oldStartIdx].elm); 
  106.         } 
  107.     } 
  108.     else if (oldStartIdx <= oldEndIdx) { 
  109.         console.log('old還有剩余節(jié)點(diǎn)沒有處理,要?jiǎng)h除項(xiàng)'); 
  110.         // 批量刪除oldStart和oldEnd指針之間的項(xiàng) 
  111.         for (let i = oldStartIdx; i <= oldEndIdx; i++) { 
  112.             if (oldCh[i]) { 
  113.                 parentElm.removeChild(oldCh[i].elm); 
  114.             } 
  115.         } 
  116.     } 

【編輯推薦】

責(zé)任編輯:姜華 來源: 前端點(diǎn)線面
相關(guān)推薦

2023-10-27 08:15:45

2023-12-12 07:31:51

Executors工具開發(fā)者

2023-12-15 09:45:21

阻塞接口

2021-04-19 17:32:34

Java內(nèi)存模型

2021-04-02 06:17:10

大數(shù)加減乘除數(shù)據(jù)結(jié)構(gòu)算法

2018-10-22 08:14:04

2024-10-16 10:11:52

2021-08-05 06:54:05

觀察者訂閱設(shè)計(jì)

2022-05-11 07:38:45

SpringWebFlux

2022-03-14 08:01:06

LRU算法線程池

2020-06-03 08:19:00

Kubernetes

2020-05-11 14:35:11

微服務(wù)架構(gòu)代碼

2022-12-20 07:39:46

2023-11-20 08:18:49

Netty服務(wù)器

2023-12-21 17:11:21

Containerd管理工具命令行

2020-05-13 09:14:16

哈希表數(shù)據(jù)結(jié)構(gòu)

2023-11-06 08:16:19

APM系統(tǒng)運(yùn)維

2021-05-29 10:11:00

Kafa數(shù)據(jù)業(yè)務(wù)

2023-07-31 08:18:50

Docker參數(shù)容器

2022-11-11 19:09:13

架構(gòu)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)