React學習筆記(八) 虛擬DOM與Diff算法

1、虛擬 DOM

(1)什麼是虛擬 DOM

我們先來回顧一下什麼是 DOM?DOM 是一個 用於表示 HTML 文檔結構 的樹,實際上它是一個 JavaScript 對象

樹上的每一個節點代表一個 HTML 元素,每個 HTML 元素擁有大量的屬性、方法和事件,是一個十分複雜的對象

所謂的虛擬 DOM 就是 用於表示真實 DOM 結構 的一個 JavaScript 對象,它與真實 DOM 之間存在一個 映射關係

每個 React 元素對應一個 HTML 元素,但是 React 元素只保留着一些必要的屬性,所以相對而言修改開銷較小

(2)爲什麼使用虛擬 DOM

當狀態發生變化時,React 不會直接更新視圖(其目的是爲了減少多次操作實際 DOM)

而是創建一個新的虛擬 DOM 緩存 所有發生變化的狀態,然後等到合適的時候 一次性 更新到瀏覽器

當組件重新渲染時,React 不會渲染整個視圖(其目的是爲了減少大量操作實際 DOM)

而是使用 Diff 算法 比較新舊虛擬 DOM 之間的差異,然後只把 真正發生變化的地方 更新到瀏覽器

用一句話概括,使用虛擬 DOM 的目的就是 提高性能,它的核心其實就是 減少實際的 DOM 操作

(3)怎麼樣使用虛擬 DOM

虛擬 DOM 是 React 實現的內部機制,它的運作過程可以粗略概括如下:

  1. 使用 JavaScript 對象創建一棵虛擬 DOM 樹,然後通過虛擬 DOM 樹構建一棵真實 DOM 樹,插入到文檔中
  2. 當狀態發生變化時,重新構造一棵虛擬 DOM 樹,比較新舊兩棵虛擬 DOM 樹之間的差異
  3. 將步驟 2 中的差異部分更新到步驟 1 中的真實 DOM 樹

2、Diff 算法

Diff 算法用於比較兩棵樹之間的差異,虛擬 DOM 之所以能極大提高性能,離不開其高效的 Diff 算法

傳統的 Diff 算法通過遞歸遍歷樹的節點比較差異,算法複雜度爲 O(n^3),性能是十分堪憂的

但是 React 的 Diff 算法可以達到 O(n),這種極大的提升完全得益於 React 的 三大 Diff 策略,下面逐一進行學習

(1)tree diff

依據:Web UI 中 DOM 節點跨層級的移動操作特別少,可以忽略不計

兩棵樹只會對 同一層級 的節點進行比較,具體來說就是 依次比較相同父節點下的所有子節點

  • 如果節點相同,那麼保留下來

  • 如果發現有節點消失,那麼直接刪除該節點及其子節點,不再往下進行比較

  • 如果發現有節點新增,那麼創建新的節點,然後繼續往下創建其子節點

這樣只要遍歷一次樹的節點就能完成比較,瞬間將算法複雜度降至 O(n),這裏舉一個例子:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XYDzF7EW-1580831034809)(React學習筆記(八)] 虛擬DOM與Diff算法\tree diff.png)

  1. 比較節點 A 的子節點 B,發現 B 消失(差異部分),所以刪除節點 B 及其子節點 D 和 E

  2. 比較節點 A 的子節點 C,發現 C 相同(沒有差異),保留

  3. 比較節點 C 的子節點 B,發現 B 新增(差異部分),所以創建節點 B 及其子節點 D 和 E

(2)component diff

依據:相同類型的組件會有相似的樹形結構,不同類型的組件會有不同的樹形結構

React 是基於組件構建應用的,它會 以粗於層級的粒度(組件) 進行比較,這裏分爲兩種情況

  • 如果是同一類型的組件,按照 tree diff 策略繼續比較

  • 如果是不同類型的組件,不會繼續比較,而是直接刪除,然後重新渲染組件

即使兩個組件的結構十分相似,如果被判斷爲不同類型,也會銷燬重建,這裏舉一個例子:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-u77hEU5R-1580831034814)(React學習筆記(八)] 虛擬DOM與Diff算法\component diff.png)

(3)element diff

依據:對於同一層級的一組節點,可以通過唯一的 key 進行區分

對於同一層級的節點,將會 以精於層級的粒度(元素) 進行比較,React 提供三種操作

  • 插入:新節點不存在於舊集合中,這樣就會插入新的節點,作出插入操作
  • 移動:新節點存在於舊集合中,且新節點只是位置有所變化,這樣就會複用舊的節點,作出移動操作
  • 刪除:新節點存在於舊集合中,但新節點有所更改不能複用,這樣就會刪除舊的節點,作出刪除操作

僞代碼如下:

oldNodes <- 舊節點集合
newNodes <- 新節點集合
lastIndex <- 0

for item in newNodes:
	if item in oldNodes:
		index <- index of item in oldNodes
		if index < lastIndex:
			在 oldNodes 中,將 item 移動到 lastIndex 位置後,相應元素前移一位
		lastIndex = max(index, lastIndex)
	else:
		在 oldNodes 中,創建 item 並將 item 插入到 lastIndex 位置後,相應元素後移一位
		lastIndex = lastIndex + 1

for item in oldNodes:
	if item not in newNodes:
    	在 oldNodes 中,刪除 item

例子如下:

在這裏插入圖片描述

【 閱讀更多 React 系列文章,請看 React學習筆記

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章