摘要:傳統(tǒng)算法通過(guò)循環(huán)遞歸對(duì)節(jié)點(diǎn)進(jìn)行依次對(duì)比,效率低下,算法復(fù)雜度達(dá)到,其中是樹(shù)中節(jié)點(diǎn)的總數(shù)。對(duì)于同一層級(jí)的一組子節(jié)點(diǎn),它們可以通過(guò)唯一進(jìn)行區(qū)分。當(dāng)發(fā)現(xiàn)節(jié)點(diǎn)已經(jīng)不存在,則該節(jié)點(diǎn)及其子節(jié)點(diǎn)會(huì)被完全刪除掉,不會(huì)用于進(jìn)一步的比較。
https://zhuanlan.zhihu.com/p/...
React diff 會(huì)幫助我們計(jì)算出 Virtual DOM 中真正變化的部分,并只針對(duì)該部分進(jìn)行實(shí)際 DOM 操作,而非重新渲染整個(gè)頁(yè)面,從而保證了每次操作更新后頁(yè)面的高效渲染,因此 Virtual DOM 與 diff 是保證 React 性能口碑的幕后推手。
計(jì)算一棵樹(shù)形結(jié)構(gòu)轉(zhuǎn)換成另一棵樹(shù)形結(jié)構(gòu)的最少操作,是一個(gè)復(fù)雜且值得研究的問(wèn)題。傳統(tǒng) diff 算法通過(guò)循環(huán)遞歸對(duì)節(jié)點(diǎn)進(jìn)行依次對(duì)比,效率低下,算法復(fù)雜度達(dá)到 O(n3),其中 n 是樹(shù)中節(jié)點(diǎn)的總數(shù)。
diff 策略Web UI 中 DOM 節(jié)點(diǎn)跨層級(jí)的移動(dòng)操作特別少,可以忽略不計(jì)。
擁有相同類的兩個(gè)組件將會(huì)生成相似的樹(shù)形結(jié)構(gòu),擁有不同類的兩個(gè)組件將會(huì)生成不同的樹(shù)形結(jié)構(gòu)。
對(duì)于同一層級(jí)的一組子節(jié)點(diǎn),它們可以通過(guò)唯一 id 進(jìn)行區(qū)分。
tree diff 通過(guò)分層求異的策略對(duì)樹(shù)進(jìn)行分層比較,兩棵樹(shù)只會(huì)對(duì)同一層次的節(jié)點(diǎn)進(jìn)行比較。既然 DOM 節(jié)點(diǎn)跨層級(jí)的移動(dòng)操作少到可以忽略不計(jì),針對(duì)這一現(xiàn)象,React 通過(guò) updateDepth 對(duì) Virtual DOM 樹(shù)進(jìn)行層級(jí)控制,只會(huì)對(duì)相同顏色方框內(nèi)的 DOM 節(jié)點(diǎn)進(jìn)行比較,即同一個(gè)父節(jié)點(diǎn)下的所有子節(jié)點(diǎn)。當(dāng)發(fā)現(xiàn)節(jié)點(diǎn)已經(jīng)不存在,則該節(jié)點(diǎn)及其子節(jié)點(diǎn)會(huì)被完全刪除掉,不會(huì)用于進(jìn)一步的比較。這樣只需要對(duì)樹(shù)進(jìn)行一次遍歷,便能完成整個(gè) DOM 樹(shù)的比較。
由于 React 只會(huì)簡(jiǎn)單的考慮同層級(jí)節(jié)點(diǎn)的位置變換,而對(duì)于不同層級(jí)的節(jié)點(diǎn),只有創(chuàng)建和刪除操作。當(dāng)出現(xiàn)節(jié)點(diǎn)跨層級(jí)移動(dòng)時(shí),并不會(huì)出現(xiàn)想象中的移動(dòng)操作,而是以 A 為根節(jié)點(diǎn)的樹(shù)被整個(gè)重新創(chuàng)建,這是一種影響 React 性能的操作,因此 React 官方建議不要進(jìn)行 DOM 節(jié)點(diǎn)跨層級(jí)的操作。
component diff 通過(guò)相同類生成相似樹(shù)形結(jié)構(gòu),不同類生成不同樹(shù)形結(jié)構(gòu)的策略React 是基于組件構(gòu)建應(yīng)用的,對(duì)于組件間的比較所采取的策略也是簡(jiǎn)潔高效。
如果是同一類型的組件,按照原策略繼續(xù)比較 virtual DOM tree。
如果不是,則將該組件判斷為 dirty component,從而替換整個(gè)組件下的所有子節(jié)點(diǎn)。
對(duì)于同一類型的組件,有可能其 Virtual DOM 沒(méi)有任何變化,如果能夠確切的知道這點(diǎn)那可以節(jié)省大量的 diff 運(yùn)算時(shí)間,因此 React 允許用戶通過(guò) shouldComponentUpdate() 來(lái)判斷該組件是否需要進(jìn)行 diff。
如下圖,當(dāng) component D 改變?yōu)?component G 時(shí),即使這兩個(gè) component 結(jié)構(gòu)相似,一旦 React 判斷 D 和 G 是不同類型的組件,就不會(huì)比較二者的結(jié)構(gòu),而是直接刪除 component D,重新創(chuàng)建 component G 以及其子節(jié)點(diǎn)。
element diff 通過(guò)設(shè)置唯一 key的策略當(dāng)節(jié)點(diǎn)處于同一層級(jí)時(shí),React diff 提供了三種節(jié)點(diǎn)操作,分別為:INSERT_MARKUP(插入)、MOVE_EXISTING(移動(dòng))和 REMOVE_NODE(刪除)。
INSERT_MARKUP,新的 component 類型不在老集合里, 即是全新的節(jié)點(diǎn),需要對(duì)新節(jié)點(diǎn)執(zhí)行插入操作。
MOVE_EXISTING,在老集合有新 component 類型,且 element 是可更新的類型,generateComponentChildren 已調(diào)用 receiveComponent,這種情況下 prevChild=nextChild,就需要做移動(dòng)操作,可以復(fù)用以前的 DOM 節(jié)點(diǎn)。
REMOVE_NODE,老 component 類型,在新集合里也有,但對(duì)應(yīng)的 element 不同則不能直接復(fù)用和更新,需要執(zhí)行刪除操作,或者老 component 不在新集合里的,也需要執(zhí)行刪除操作。
允許開(kāi)發(fā)者對(duì)同一層級(jí)的同組子節(jié)點(diǎn),添加唯一 key 進(jìn)行區(qū)分,雖然只是小小的改動(dòng),性能上卻發(fā)生了翻天覆地的變化!
先對(duì)新集合的節(jié)點(diǎn)進(jìn)行循環(huán)遍歷,for (name in nextChildren),通過(guò)唯一 key 可以判斷新老集合中是否存在相同的節(jié)點(diǎn),if (prevChild === nextChild),如果存在相同節(jié)點(diǎn),則進(jìn)行移動(dòng)操作,但在移動(dòng)前需要將當(dāng)前節(jié)點(diǎn)在老集合中的位置與 lastIndex 進(jìn)行比較,if (child._mountIndex < lastIndex),則進(jìn)行節(jié)點(diǎn)移動(dòng)操作,否則不執(zhí)行該操作。==這是一種順序優(yōu)化手段,lastIndex 一直在更新,表示訪問(wèn)過(guò)的節(jié)點(diǎn)在老集合中最右的位置(即最大的位置)==,如果新集合中當(dāng)前訪問(wèn)的節(jié)點(diǎn)比 lastIndex 大,說(shuō)明當(dāng)前訪問(wèn)節(jié)點(diǎn)在老集合中就比上一個(gè)節(jié)點(diǎn)位置靠后,則該節(jié)點(diǎn)不會(huì)影響其他節(jié)點(diǎn)的位置,因此不用添加到差異隊(duì)列中,即不執(zhí)行移動(dòng)操作,只有當(dāng)訪問(wèn)的節(jié)點(diǎn)比 lastIndex 小時(shí),才需要進(jìn)行移動(dòng)操作。
總結(jié)React 通過(guò)制定大膽的 diff 策略,將 O(n3) 復(fù)雜度的問(wèn)題轉(zhuǎn)換成 O(n) 復(fù)雜度的問(wèn)題;
React 通過(guò)分層求異的策略,對(duì) tree diff 進(jìn)行算法優(yōu)化;
React 通過(guò)相同類生成相似樹(shù)形結(jié)構(gòu),不同類生成不同樹(shù)形結(jié)構(gòu)的策略,對(duì) component diff 進(jìn)行算法優(yōu)化;
React 通過(guò)設(shè)置唯一 key的策略,對(duì) element diff 進(jìn)行算法優(yōu)化;
建議,在開(kāi)發(fā)組件時(shí),保持穩(wěn)定的 DOM 結(jié)構(gòu)會(huì)有助于性能的提升;
建議,在開(kāi)發(fā)過(guò)程中,盡量減少類似將最后一個(gè)節(jié)點(diǎn)移動(dòng)到列表首部的操作,當(dāng)節(jié)點(diǎn)數(shù)量過(guò)大或更新操作過(guò)于頻繁時(shí),在一定程度上會(huì)影響 React 的渲染性能。
Diff AlgorithmLevel by Level
List
Components
Event Delegation RenderingBatching
Sub-tree Rendering
Selective Sub-tree Rendering
diff策略oldVdom不存在時(shí),將newVdom生成的dom添加到父元素
newVdom不存在時(shí),將newVdom對(duì)應(yīng)index的真實(shí)dom刪除
oldVdom, newVdom 的根節(jié)點(diǎn)不一致時(shí),直接將oldVdom替換為newVdom
若上述都不滿足,則說(shuō)明兩個(gè)vdom的根節(jié)點(diǎn)是一致的, 然后遞歸調(diào)用 diff & patch 方法
function h(type, props, ...children) { return {type, props, children}; } function createElement(node) { if (typeof node === "string") { return document.createTextNode(node); } const $el = document.createElement(node.type); node.children .map(createElement) .forEach($el.appendChild.bind($el)); return $el; } function changed(node1, node2) { return typeof node1 !== typeof node2 || typeof node1 === "string" && node1 !== node2 || node1.type !== node2.type } function updateElement($parent, newNode, oldNode, index = 0) { console.log(Array.from(arguments)) // console.log(newNode) // console.log(newNode) if (!oldNode) { $parent.appendChild( createElement(newNode) ); } else if (!newNode) { $parent.removeChild( $parent.childNodes[index] ); } else if (changed(newNode, oldNode)) { console.log("if go changed") console.log(newNode, oldNode) $parent.replaceChild( createElement(newNode), $parent.childNodes[index] ); } else if (newNode.type) { console.log("test if go last if") const newLength = newNode.children.length; const oldLength = oldNode.children.length; for (let i = 0; i < newLength || i < oldLength; i++) { updateElement( $parent.childNodes[index], newNode.children[i], oldNode.children[i], i ); } } } // --------------------------------------------------------------------- // let a = ( //
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/88964.html
摘要:并且處理特殊屬性,比如事件綁定。之后根據(jù)差異對(duì)象操作元素位置變動(dòng),刪除,添加等。當(dāng)節(jié)點(diǎn)數(shù)過(guò)大或者頁(yè)面更新次數(shù)過(guò)多時(shí),頁(yè)面卡頓的現(xiàn)象會(huì)比較明顯?;谧⒁馐褂脕?lái)減少組件不必要的更新。 1、什么是Diff算法 傳統(tǒng)Diff:diff算法即差異查找算法;對(duì)于Html DOM結(jié)構(gòu)即為tree的差異查找算法;而對(duì)于計(jì)算兩顆樹(shù)的差異時(shí)間復(fù)雜度為O(n^3),顯然成本太高,React不可能采用這種...
摘要:并且處理特殊屬性,比如事件綁定。之后根據(jù)差異對(duì)象操作元素位置變動(dòng),刪除,添加等。當(dāng)節(jié)點(diǎn)數(shù)過(guò)大或者頁(yè)面更新次數(shù)過(guò)多時(shí),頁(yè)面卡頓的現(xiàn)象會(huì)比較明顯?;谧⒁馐褂脕?lái)減少組件不必要的更新。 1、什么是Diff算法 傳統(tǒng)Diff:diff算法即差異查找算法;對(duì)于Html DOM結(jié)構(gòu)即為tree的差異查找算法;而對(duì)于計(jì)算兩顆樹(shù)的差異時(shí)間復(fù)雜度為O(n^3),顯然成本太高,React不可能采用這種...
摘要:但是加了一定要比沒(méi)加的性能更高嗎我們?cè)賮?lái)看一個(gè)例子現(xiàn)在有一集合渲染成如下的樣子現(xiàn)在我們將這個(gè)集合的順序打亂變成。不加操作修改第個(gè)到第個(gè)節(jié)點(diǎn)的如果我們對(duì)這個(gè)集合進(jìn)行增刪的操作改成。 拋磚引玉 React通過(guò)引入Virtual DOM的概念,極大地避免無(wú)效的Dom操作,已使我們的頁(yè)面的構(gòu)建效率提到了極大的提升。但是如何高效地通過(guò)對(duì)比新舊Virtual DOM來(lái)找出真正的Dom變化之處同樣也...
摘要:的一個(gè)突出特點(diǎn)是擁有極速地渲染性能。該功能依靠的就是研發(fā)團(tuán)隊(duì)弄出的虛擬機(jī)制以及其獨(dú)特的算法。在的算法下,在同一位置對(duì)比前后節(jié)點(diǎn)只要發(fā)現(xiàn)不同,就會(huì)刪除操作前的節(jié)點(diǎn)包括其子節(jié)點(diǎn),替換為操作后的節(jié)點(diǎn)。 React的一個(gè)突出特點(diǎn)是擁有極速地渲染性能。該功能依靠的就是facebook研發(fā)團(tuán)隊(duì)弄出的虛擬dom機(jī)制以及其獨(dú)特的diff算法。下面簡(jiǎn)單解釋一下react虛擬dom機(jī)制和diff算法的實(shí)現(xiàn)...
摘要:目前,前端領(lǐng)域中勢(shì)頭正盛,使用者眾多卻少有能夠深入剖析內(nèi)部實(shí)現(xiàn)機(jī)制和原理。當(dāng)發(fā)現(xiàn)節(jié)點(diǎn)已經(jīng)不存在,則該節(jié)點(diǎn)及其子節(jié)點(diǎn)會(huì)被完全刪除掉,不會(huì)用于進(jìn)一步的比較。 目前,前端領(lǐng)域中 React 勢(shì)頭正盛,使用者眾多卻少有能夠深入剖析內(nèi)部實(shí)現(xiàn)機(jī)制和原理。本系列文章希望通過(guò)剖析 React 源碼,理解其內(nèi)部的實(shí)現(xiàn)原理,知其然更要知其所以然。 React diff 作為 Virtual DOM 的加速...
閱讀 3153·2021-10-13 09:39
閱讀 2763·2021-09-27 13:34
閱讀 2119·2019-08-30 15:55
閱讀 3315·2019-08-30 15:43
閱讀 3713·2019-08-30 11:16
閱讀 1837·2019-08-26 18:28
閱讀 1416·2019-08-26 13:56
閱讀 1012·2019-08-26 13:35