成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一文帶你徹底搞定Diff算法

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

[[420540]]

一、基礎

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

1.虛擬DOM

虛擬DOM指的就是將真實的DOM樹構造為js對象的形式,從而解決瀏覽器操作真實DOM的性能問題。

例如:如下DOM與虛擬DOM之間的映射關系

2.最小量更新

Diff的用途就是在新老虛擬DOM之間找到最小更新的部分,從而將該部分對應的DOM進行更新。

二、整個流程

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

  1. 首先比較一下新舊節點是不是同一個節點(可通過比較sel(選擇器)和key(唯一標識)值是不是相同),不是同一個節點則進行暴力刪除(注:先以舊節點為基準插入新節點,然后再刪除舊節點)。
  2. 若是同一個節點則需要進一步比較

完全相同,不做處理

新節點內容為文本,直接替換完事

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

三、實戰

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

3.1 patch函數

Diff算法的入口函數,主要判斷新舊節點是不是同一個節點,然后交由不同的邏輯進行處理。

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

3.2 patchVnode函數

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

  1. export default function patchVnode(oldVnode, newVnode) { 
  2.     // 判斷新舊vnode是否是同一個對象 
  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.             // 清空老的節點的內容 
  24.             oldVnode.elm.innerHTML = ''
  25.             // 遍歷新的vnode的子節點,創建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函數

核心函數,主要負責舊虛擬節點和新虛擬節點均存在子元素的情況,按照比較策略依次進行比較,最終找出子元素中變化的部分,實現最小更新。對于該部分,涉及到一些指針,如下所示:

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

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

  1. 命中“新后舊前”策略,然后將信后對應的DOM節點(也就是節點1)移動到舊后節點(節點3)后面,然后舊前節點指針下移,新后節點指針上移。
  2. 仍然命中“新后舊前”策略,做相同的操作,將節點2移動到舊后節點(節點3)后面,下移舊前節點,上移新后節點。
  3. 命中“新前舊前”策略,DOM節點不變,舊前和新前節點均下移。
  4. 跳出循環,移動結束。
  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.     // 舊前節點 
  11.     let oldStartVnode = oldCh[0]; 
  12.     // 舊后節點 
  13.     let oldEndVnode = oldCh[oldEndIdx]; 
  14.     // 新前節點 
  15.     let newStartVnode = newCh[0]; 
  16.     // 新后節點 
  17.     let newEndVnode = newCh[newEndIdx]; 
  18.  
  19.     let keyMap = null
  20.  
  21.     while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { 
  22.         // 略過已經加undefined標記的內容 
  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.             // 當新后與舊前命中的時候,此時要移動節點,移動新后指向的這個節點到老節點舊后的后面 
  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.             // 當新前和舊后命中的時候,此時要移動節點,移動新前指向的這個節點到老節點舊前的前面 
  62.             parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm); 
  63.             oldEndVnode = oldCh[--oldEndIdx]; 
  64.             newStartVnode = newCh[++newStartIdx]; 
  65.         } 
  66.         else { 
  67.             // 四種都沒有命中 
  68.             // 制作keyMap一個映射對象,這樣就不用每次都遍歷老對象了 
  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.             // 尋找當前這項(newStartIdx)在keyMap中的映射的位置序號 
  79.             const idxInOld = keyMap[newStartVnode.key]; 
  80.             if (idxInOld === undefined) { 
  81.                 // 如果idxInOld是undefined表示踏實全新的項,此時會將該項創建為DOM節點并插入到舊前之前 
  82.                 parentElm.insertBefore(createElement(newStartVnode), oldStartVnode.elm); 
  83.             } 
  84.             else { 
  85.                 // 如果不是undefined,則不是全新的項,則需要移動 
  86.                 const elmToMove = oldCh[idxInOld]; 
  87.                 patchVnode(elmToMove, newStartVnode); 
  88.                 // 把這項設置為undefined,表示已經處理完這項了 
  89.                 oldCh[idxInOld] = undefined; 
  90.                 // 移動 
  91.                 parentElm.insertBefore(elmToMove.elm, oldStartVnode.elm); 
  92.             } 
  93.             // 指針下移,只移動新的頭 
  94.             newStartVnode = newCh[++newStartIdx]; 
  95.         } 
  96.     } 
  97.  
  98.     // 循環結束后,處理未處理的項 
  99.     if (newStartIdx <= newEndIdx) { 
  100.         console.log('new還有剩余節點沒有處理,要加項,把所有剩余的節點插入到oldStartIdx之前'); 
  101.         // 遍歷新的newCh,添加到老的沒有處理的之前 
  102.         for (let i = newStartIdx; i <= newEndIdx; i++) { 
  103.             // insertBefore方法可以自動識別null,如果是null就會自動排到隊尾去 
  104.             // newCh[i]現在還沒有真正的DOM,所以要調用createElement函數變為DOM 
  105.             parentElm.insertBefore(createElement(newCh[i]), oldCh[oldStartIdx].elm); 
  106.         } 
  107.     } 
  108.     else if (oldStartIdx <= oldEndIdx) { 
  109.         console.log('old還有剩余節點沒有處理,要刪除項'); 
  110.         // 批量刪除oldStart和oldEnd指針之間的項 
  111.         for (let i = oldStartIdx; i <= oldEndIdx; i++) { 
  112.             if (oldCh[i]) { 
  113.                 parentElm.removeChild(oldCh[i].elm); 
  114.             } 
  115.         } 
  116.     } 

【編輯推薦】

責任編輯:姜華 來源: 前端點線面
相關推薦

2023-10-27 08:15:45

2023-12-15 09:45:21

阻塞接口

2023-12-12 07:31:51

Executors工具開發者

2021-04-19 17:32:34

Java內存模型

2018-10-22 08:14:04

2021-04-02 06:17:10

大數加減乘除數據結構算法

2021-08-05 06:54:05

觀察者訂閱設計

2024-10-16 10:11:52

2022-05-11 07:38:45

SpringWebFlux

2022-03-14 08:01:06

LRU算法線程池

2020-06-03 08:19:00

Kubernetes

2022-12-20 07:39:46

2023-11-20 08:18:49

Netty服務器

2023-12-21 17:11:21

Containerd管理工具命令行

2020-05-11 14:35:11

微服務架構代碼

2020-05-13 09:14:16

哈希表數據結構

2021-05-29 10:11:00

Kafa數據業務

2023-07-31 08:18:50

Docker參數容器

2023-11-06 08:16:19

APM系統運維

2022-11-11 19:09:13

架構
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久久蜜桃 | 免费亚洲成人 | 美女视频一区二区三区 | 99久久国产免费 | 丁香婷婷在线视频 | 日韩精品在线视频 | 国产亚洲欧美在线视频 | 免费在线观看h片 | av激情影院 | 色综合久 | 亚洲精品一区二区冲田杏梨 | 色毛片 | 精品久久精品 | 日韩色在线 | 午夜精品久久久 | 国内精品在线视频 | 中文字幕在线一区 | 欧美在线观看免费观看视频 | 欧美日韩在线精品 | 中文字幕人成乱码在线观看 | 男人的天堂亚洲 | 久久久久久国模大尺度人体 | 国产精品久久久久久久岛一牛影视 | 国产一区二区三区久久久久久久久 | 蜜臀网站 | 欧美日韩亚洲三区 | 色就是色欧美 | 日韩三级电影一区二区 | 国产资源在线观看 | 国产一区欧美 | 99小视频 | 日韩在线播放第一页 | 欧美日韩视频 | 91资源在线 | 拍真实国产伦偷精品 | 国产精品久久久久久吹潮 | 中文字幕免费中文 | 成人午夜在线 | 亚洲精品视频在线看 | 亚洲情综合五月天 | 天天操操 |