react diff算法淺析

diff算法做爲Virtual DOM的加速器,其算法的改進優化是React整個界面渲染的基礎和性能的保障,同時也是React源碼中最神祕的,最難以想象的部分html

1.傳統diff算法
計算一棵樹形結構轉換爲另外一棵樹形結構須要最少步驟,若是使用傳統的diff算法經過循環遞歸遍歷節點進行對比,其複雜度要達到O(n^3),其中n是節點總數,效率十分低下,假設咱們要展現1000個節點,那麼咱們就要依次執行上十億次的比較。react

下面附上一則簡單的傳統diff算法:算法


let result = [];
// 比較葉子節點
const diffLeafs = function (beforeLeaf, afterLeaf) {
// 獲取較大節點樹的長度
let count = Math.max(beforeLeaf.children.length, afterLeaf.children.length);
// 循環遍歷
for (let i = 0; i < count; i++) {
const beforeTag = beforeLeaf.children[i];
const afterTag = afterLeaf.children[i];
// 添加 afterTag 節點
if (beforeTag === undefined) {
result.push({ type: "add", element: afterTag });
// 刪除 beforeTag 節點
} else if (afterTag === undefined) {
result.push({ type: "remove", element: beforeTag });
// 節點名改變時,刪除 beforeTag 節點,添加 afterTag 節點
} else if (beforeTag.tagName !== afterTag.tagName) {
result.push({ type: "remove", element: beforeTag });
result.push({ type: "add", element: afterTag });
// 節點不變而內容改變時,改變節點
} else if (beforeTag.innerHTML !== afterTag.innerHTML) {
if (beforeTag.children.length === 0) {
result.push({
type: "changed",
beforeElement: beforeTag,
afterElement: afterTag,
html: afterTag.innerHTML
});
} else {
// 遞歸比較
diffLeafs(beforeTag, afterTag);
}
}
}
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2.react diff算法
1. diff策略
下面介紹一下react diff算法的3個策略性能

Web UI 中DOM節點跨層級的移動操做特別少,能夠忽略不計
擁有相同類的兩個組件將會生成類似的樹形結構,擁有不一樣類的兩個組件將會生成不一樣的樹形結構。
對於同一層級的一組子節點,它們能夠經過惟一id進行區分。
對於以上三個策略,react分別對tree diff,component diff,element diff進行算法優化。優化

2.tree diff
基於策略一,WebUI中DOM節點跨層級的移動操做少的能夠忽略不計,React對Virtual DOM樹進行層級控制,只會對相同層級的DOM節點進行比較,即同一個父元素下的全部子節點,當發現節點已經不存在了,則會刪除掉該節點下全部的子節點,不會再進行比較。這樣只須要對DOM樹進行一次遍歷,就能夠完成整個樹的比較。複雜度變爲O(n);component

疑問:當咱們的DOM節點進行跨層級操做時,diff會有怎麼樣的表現呢?htm

以下圖所示,A節點及其子節點被整個移動到D節點下面去,因爲React只會簡單的考慮同級節點的位置變換,而對於不一樣層級的節點,只有建立和刪除操做,因此當根節點發現A節點消失了,就會刪除A節點及其子節點,當D發現多了一個子節點A,就會建立新的A做爲其子節點。
此時,diff的執行狀況是:遞歸

createA-->createB-->createC-->deleteA
1

由此能夠發現,當出現節點跨層級移動時,並不會出現想象中的移動操做,而是會進行刪除,從新建立的動做,這是一種很影響React性能的操做。所以官方也不建議進行DOM節點跨層級的操做。element

3.componnet diff
React是基於組件構建應用的,對於組件間的比較所採用的策略也是很是簡潔和高效的。開發

若是是同一個類型的組件,則按照原策略進行Virtual DOM比較。
若是不是同一類型的組件,則將其判斷爲dirty component,從而替換整個組價下的全部子節點。
若是是同一個類型的組件,有可能通過一輪Virtual DOM比較下來,並無發生變化。若是咱們可以提早確切知道這一點,那麼就能夠省下大量的diff運算時間。所以,React容許用戶經過shouldComponentUpdate()來判斷該組件是否須要進行diff算法分析。
以下圖所示,當組件D變爲組件G時,即便這兩個組件結構類似,一旦React判斷D和G是不用類型的組件,就不會比較二者的結構,而是直接刪除組件D,從新建立組件G及其子節點。雖然當兩個組件是不一樣類型但結構類似時,進行diff算法分析會影響性能,可是畢竟不一樣類型的組件存在類似DOM樹的狀況在實際開發過程當中不多出現,所以這種極端因素很難在實際開發過程當中形成重大影響。


4.element diff
當節點屬於同一層級時,diff提供了3種節點操做,分別爲INSERT_MARKUP(插入),MOVE_EXISTING(移動),REMOVE_NODE(刪除)。

INSERT_MARKUP:新的組件類型不在舊集合中,即全新的節點,須要對新節點進行插入操做。MOVE_EXISTING:舊集合中有新組件類型,且element是可更新的類型,這時候就須要作移動操做,能夠複用之前的DOM節點。REMOVE_NODE:舊組件類型,在新集合裏也有,但對應的element不一樣則不能直接複用和更新,須要執行刪除操做,或者舊組件不在新集合裏的,也須要執行刪除操做。

相關文章
相關標籤/搜索