網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
一、Diff算法的作用
渲染真實(shí)DOM的開銷很大,有時(shí)候我們修改了某個(gè)數(shù)據(jù),直接渲染到真實(shí)dom上會(huì)引起整個(gè)dom樹的重繪和重排。我們希望只更新我們修改的那一小塊dom,而不是整個(gè)dom,diff算法就幫我們實(shí)現(xiàn)了這點(diǎn)。
diff算法的本質(zhì)就是:找出兩個(gè)對(duì)象之間的差異,目的是盡可能做到節(jié)點(diǎn)復(fù)用。
注:此處說(shuō)到的對(duì)象,指的其實(shí)就是vue中的virtual dom(虛擬dom樹),即使用js對(duì)象來(lái)表示頁(yè)面中的dom結(jié)構(gòu)。
二、React的Diff算法??
1、什么是調(diào)和?
將Virtual DOM樹轉(zhuǎn)換成Actual DOM樹的最少操作的過(guò)程稱為調(diào)和。
2、什么是React diff算法?
diff算法是調(diào)和的具體實(shí)現(xiàn)。
3、diff策略
React用三大策略 將O(n3)復(fù)雜度 轉(zhuǎn)化為O(n)復(fù)雜度
(1)策略一(tree diff
):Web UI中DOM節(jié)點(diǎn)跨層級(jí)的移動(dòng)操作特別少,可以忽略不計(jì)。
(2)策略二(component diff
):擁有相同類的兩個(gè)組件 生成相似的樹形結(jié)構(gòu),擁有不同類的兩個(gè)組件 生成不同的樹形結(jié)構(gòu)。
(3)策略三(element diff
):對(duì)于同一層級(jí)的一組子節(jié)點(diǎn),通過(guò)唯一id區(qū)分。
4、tree diff:
(1)React通過(guò)updateDepth
對(duì)Virtual DOM
樹進(jìn)行層級(jí)控制。
(2)對(duì)樹分層比較,兩棵樹只對(duì)同一層次節(jié)點(diǎn)進(jìn)行比較。如果該節(jié)點(diǎn)不存在時(shí),則該節(jié)點(diǎn)及其子節(jié)點(diǎn)會(huì)被完全刪除,不會(huì)再進(jìn)一步比較。
(3)只需遍歷一次,就能完成整棵DOM樹的比較。
如果DOM 節(jié)點(diǎn)出現(xiàn)了跨層級(jí)操作,Diff會(huì)怎么辦?
答:Tree DIFF是對(duì)樹的每一層進(jìn)行遍歷,如果某組件不存在了,則會(huì)直接銷毀。如圖所示,左邊是舊屬,右邊是新屬,第一層是R組件,一模一樣,不會(huì)發(fā)生變化;第二層進(jìn)入Component DIFF,同一類型組件繼續(xù)比較下去,發(fā)現(xiàn)A組件沒有,所以直接刪掉A、B、C組件;繼續(xù)第三層,重新創(chuàng)建A、B、C組件。
如上圖所示,以A為根節(jié)點(diǎn)的整棵樹會(huì)被重新創(chuàng)建,而不是移動(dòng),因此?官方建議不要進(jìn)行DOM節(jié)點(diǎn)跨層級(jí)操作,可以通過(guò)CSS隱藏、顯示節(jié)點(diǎn),而不是真正地移除、添加DOM節(jié)點(diǎn)。
5、component diff :
React對(duì)不同的組件間的比較,有三種策略
(1)同一類型的兩個(gè)組件,按原策略(層級(jí)比較)繼續(xù)比較Virtual DOM樹即可。
(2)同一類型的兩個(gè)組件,組件A變化為組件B時(shí),可能Virtual DOM沒有任何變化,如果知道這點(diǎn)(變換的過(guò)程中,Virtual DOM沒有改變),可節(jié)省大量計(jì)算時(shí)間,所以用戶可以通過(guò) shouldComponentUpdate()
來(lái)判斷是否需要判斷計(jì)算。
(3)不同類型的組件,將一個(gè)(將被改變的)組件判斷為dirtycomponent
(臟組件),從而替換整個(gè)組件的所有節(jié)點(diǎn)。
?注意:如上圖所示,當(dāng)組件D變?yōu)榻M件G時(shí),即使這兩個(gè)組件結(jié)構(gòu)相似,一旦React判斷D和G是不用類型的組件,就不會(huì)比較兩者的結(jié)構(gòu),而是直接刪除組件D,重新創(chuàng)建組件G及其子節(jié)點(diǎn)。
雖然當(dāng)兩個(gè)組件是不同類型但結(jié)構(gòu)相似時(shí),進(jìn)行diff算法分析會(huì)影響性能,但是畢竟不同類型的組件存在相似DOM樹的情況在實(shí)際開發(fā)過(guò)程中很少出現(xiàn),因此這種極端因素很難在實(shí)際開發(fā)過(guò)程中造成重大影響。
6、element diff?
當(dāng)節(jié)點(diǎn)處于同一層級(jí)時(shí),diff提供三種節(jié)點(diǎn)操作:刪除、插入、移動(dòng)。
插入:組件 C 不在集合(A,B)中,需要插入
刪除:
(1)組件 D 在集合(A,B,D)中,但 D的節(jié)點(diǎn)已經(jīng)更改,不能復(fù)用和更新,所以需要?jiǎng)h除 舊的D ,再創(chuàng)建新的。
(2)組件D之前在集合(A,B,D)中,但集合變成新的集合(A,B)了,D 就需要被刪除。
移動(dòng):組件D已經(jīng)在集合(A,B,C,D)里了,且集合更新時(shí),D沒有發(fā)生更新,只是位置改變,如新集合(A,D,B,C),D在第二個(gè),無(wú)須像傳統(tǒng)diff,讓舊集合的第二個(gè)B和新集合的第二個(gè)D 比較,并且刪除第二個(gè)位置的B,再在第二個(gè)位置插入D,而是 (對(duì)同一層級(jí)的同組子節(jié)點(diǎn)) 添加唯一key進(jìn)行區(qū)分,移動(dòng)即可。
?移動(dòng)情形一:新舊集合中存在相同節(jié)點(diǎn)但位置不同時(shí),如何移動(dòng)節(jié)點(diǎn)
(1)B不移動(dòng),不贅述,更新l astIndex=1
(2)新集合取得 E,發(fā)現(xiàn)舊不存在,故在lastIndex=1的位置 創(chuàng)建E,更新lastIndex=1
(3)新集合取得C,C不移動(dòng),更新lastIndex=2
(4)新集合取得A,A移動(dòng),同上,更新lastIndex=2
(5)新集合對(duì)比后,再對(duì)舊集合遍歷。判斷 新集合 沒有,但 舊集合 有的元素(如D,新集合沒有,舊集合有),發(fā)現(xiàn) D,刪除D,diff操作結(jié)束。
React中Diff算法實(shí)現(xiàn)的代碼:
_updateChildren: function(nextNestedChildrenElements, transaction, context) { var prevChildren = this._renderedChildren; var removedNodes = {}; var mountImages = []; // 獲取新的子元素?cái)?shù)組 var nextChildren = this._reconcilerUpdateChildren( prevChildren, nextNestedChildrenElements, mountImages, removedNodes, transaction, context ); if (!nextChildren && !prevChildren) { return; } var updates = null; var name; var nextIndex = 0; var lastIndex = 0; var nextMountIndex = 0; var lastPlacedNode = null; for (name in nextChildren) { if (!nextChildren.hasOwnProperty(name)) { continue; } var prevChild = prevChildren && prevChildren[name]; var nextChild = nextChildren[name]; if (prevChild === nextChild) { // 同一個(gè)引用,說(shuō)明是使用的同一個(gè)component,所以我們需要做移動(dòng)的操作 // 移動(dòng)已有的子節(jié)點(diǎn) // NOTICE:這里根據(jù)nextIndex, lastIndex決定是否移動(dòng) updates = enqueue( updates, this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex) ); // 更新lastIndex lastIndex = Math.max(prevChild._mountIndex, lastIndex); // 更新component的.mountIndex屬性 prevChild._mountIndex = nextIndex; } else { if (prevChild) { // 更新lastIndex lastIndex = Math.max(prevChild._mountIndex, lastIndex); } // 添加新的子節(jié)點(diǎn)在指定的位置上 updates = enqueue( updates, this._mountChildAtIndex( nextChild, mountImages[nextMountIndex], lastPlacedNode, nextIndex, transaction, context ) ); nextMountIndex++; } // 更新nextIndex nextIndex++; lastPlacedNode = ReactReconciler.getHostNode(nextChild); } // 移除掉不存在的舊子節(jié)點(diǎn),和舊子節(jié)點(diǎn)和新子節(jié)點(diǎn)不同的舊子節(jié)點(diǎn) for (name in removedNodes) { if (removedNodes.hasOwnProperty(name)) { updates = enqueue( updates, this._unmountChild(prevChildren[name], removedNodes[name]) ); } } }
三、基于Diff的開發(fā)建議
基于tree diff:
- 開發(fā)組件時(shí),注意保持DOM結(jié)構(gòu)的穩(wěn)定;即,盡可能少地動(dòng)態(tài)操作DOM結(jié)構(gòu),尤其是移動(dòng)操作。
- 當(dāng)節(jié)點(diǎn)數(shù)過(guò)大或者頁(yè)面更新次數(shù)過(guò)多時(shí),頁(yè)面卡頓的現(xiàn)象會(huì)比較明顯。
- 這時(shí)可以通過(guò) CSS 隱藏或顯示節(jié)點(diǎn),而不是真的移除或添加 DOM 節(jié)點(diǎn)。
基于component diff:
- 注意使用 shouldComponentUpdate() 來(lái)減少組件不必要的更新。
- 對(duì)于類似的結(jié)構(gòu)應(yīng)該盡量封裝成組件,既減少代碼量,又能減少component diff的性能消耗。
基于element diff:
- 對(duì)于列表結(jié)構(gòu),盡量減少類似將最后一個(gè)節(jié)點(diǎn)移動(dòng)到列表首部的操作,當(dāng)節(jié)點(diǎn)數(shù)量過(guò)大或更新操作過(guò)于頻繁時(shí),在一定程度上會(huì)影響 React 的渲染性能。
總結(jié)
原文鏈接:https://blog.csdn.net/m0_53644435/article/details/123440036
相關(guān)推薦
- 2022-09-08 Docker容器使用方法詳解_docker
- 2022-11-08 Go中init()執(zhí)行順序詳解_Golang
- 2022-12-13 C++實(shí)現(xiàn)一個(gè)簡(jiǎn)易版的事件(Event)的示例代碼_C 語(yǔ)言
- 2023-06-17 C#中Stopwatch的使用及說(shuō)明_C#教程
- 2022-09-01 ASP.NET輕量級(jí)MVC框架Nancy的基本用法_實(shí)用技巧
- 2022-06-14 Golang監(jiān)聽日志文件并發(fā)送到kafka中_Golang
- 2022-07-19 JDBC BLOB文件存取
- 2023-02-18 Framework中實(shí)現(xiàn)OC和Swift的混編方案_IOS
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支