摘要:比較虛擬與的差異,以及對節點的操作,其實就是樹的差異比較,就是對樹的節點進行替換。忽略掉這種特殊的情況后,大膽的修改了算法按直系兄弟節點比較比較。這當中對比的細節才是整個算法最精粹的地方。
一、舊社會的頁面渲染
???????在jQuery橫行的時代,FEer們,通過各種的方式去對頁面的DOM進行操作,計算大小,變化,來讓頁面生動活潑起來,豐富的DOM操作,讓一個表面簡單的頁面能展示出花一般的操作。
???????這個時候,人們通過DOM簡單的方法去對頁面DOM結構作出操作和改變,每一次數據的革新,都通過復雜的操作去執行變化。直到有人指出,瀏覽器對于頁面的paint和layout是有性能消耗的,多次layout會占用大量的瀏覽器計算內存。
???????于是人們開始想辦法去減少layout,盡可能的去利用class改變,fragment包裹,display的設置來操作dom,避免瀏覽器的layout發生。甚至還有人根據瀏覽器渲染的頻率,通過requestAnimationFrame的回調來觸發渲染。
???????然而,隨著三大框架的誕生,有一群人站出來呼吁大家放棄掉對dom的操作,用狀態去控制組件的生命,用狀態去控制頁面的變化,而與dom打交道的事,交給他們來做就行了……
二、React的DOM模擬???????每一個頁面都是由DOM節點構成的,這可以從DOM的原意Document Object Model 文檔對象模型看出來,頁面文檔都是通過DOM節點組成,這是HTML的基架。因此而言,想要頁面是可動態變化的,就不得不去對頁面做一些操作。fragment 和 requestAnimationFrame 的使用,告訴了我們,頁面的操作其實可以通過一個異步的形式來完成,將多次操作合并在一次當中執行,根據一定的周期去重新改變頁面的顯示。
???????但是很大的一個痛點就是,頁面中存在多個地方的內容需要變化,那我在最后的變化關頭應該如何取舍對dom的操作呢?我如何知道,哪些需要變化,哪些不需要變化呢?
???????react的開發者明確了一個首要的問題,那就是如何衡量dom什么時候需要變化,什么位置需要變化。
???????眾所周知,每一個DOM節點都帶有自身的屬性,一個簡單的div上面掛載了幾十個attributes,顯然dom其實可以看作一個對象,這是就是虛擬dom的原型——以對象的形式去模擬dom,這樣子操作前后的dom就可以量化他們的區別,dom的比較就能被開發者握在手中。通過每次操作去變化dom上各對象,子對象的屬性,最后統一與現有dom結構做對比,就能計算出當中的差異,最后去對這些差異進行dom操作,就可以簡單的對頁面進行變更。
???????虛擬dom(VirtualDOM)由此產生,它是平行于dom的另一套對象體系,用于記錄dom的狀態,我上一篇文章對setState的介紹,其實就是對虛擬dom屬性的變化操作,其中的batching就是虛擬dom最核心的阻塞功能,通過事務去控制數據在一個周期內不做多次更新,通過state去保證最后的狀態即為要更新的狀態。
???????有了虛擬dom,對頁面元素進行了抽象,其實才是虛擬dom最重要的意義,這種概念讓react有了’一次編寫,處處運行’的堅實基礎。
???????既然知道虛擬dom的實際是帶有屬性的對象,那么我們解決了第一個問題,也就是如何衡量dom什么時候需要變化,接下來就介紹一下如何衡量,什么位置需要變化?
???????虛擬dom和dom有一個共同的特點,就是樹形結構。比較虛擬dom與dom的差異,以及對dom節點的操作,其實就是樹的差異比較,就是對樹的節點進行替換。對于樹的比較,有一個最簡單的方法就是循環遞歸比較樹的節點,也就是傳統的diff算法,這個算法的復雜度達到了立方級別,效率可以說很差。
???????而react使用diff算法的時候,根據應用場景大膽的改變了算法本身的難度,硬生生把算法復雜度降到了O(n)
???????react diff遵循三個策略:
webUI種,dom節點跨層級移動操作特別少,所以忽略這種操作
擁有相同類的兩個組件將會生成相似的樹形結構,擁有不同類的兩個組件將會生成不同的樹形結構
對于同一層級的一組子節點,它們可以通過唯一id進行區分
???????這么聽下來,感覺這三個策略莫名奇妙,接下來,我通過diff算法在不同層次的比較,介紹diff算法如何去利用這三個策略高效更新組件的
四、tree diff與跨層級dom操作???????所謂的跨層級dom操作是指,原本在dom節點A下面的一個組件a,被移動到dom節點B下面。這種操作我見過最常見的應用場景是把左側區域的數據添加到右側區域,然而除了這種業務場景之外,很少有把一個dom從自身的容器中移到另一個容器中的情況,因此,策略一,這種操作在react當中默認是很少有的操作。
???????忽略掉這種特殊的情況后,react大膽的修改了diff算法——按直系兄弟節點比較比較。多個組件擁有同一個父組件,歸為一個組,整組與變化后的情況做對比,缺了就補上,多了就刪除。一層一層的,從上至下進行按層的比較。這種比較過程中,如果發現某一個節點發生變化,可以直接不遍歷其子節點,直接統一刪除整個節點以及其子節點,把新節點直接替換上去,這樣子大大減少了遍歷的消耗。
???????基于這種’粗暴的方式’來修改頁面dom,如果是進行跨層級操作,則會刪除一個完整節點,再新增一個完整節點,這是高消耗的事情,所以react考慮到跨層級操作很少,所以作出了react diff的優化。
五、componet diffreact當中,組件的概念貫通全局,所以到了組件新舊的比較上,react直接給出策略:
如果是同一類型組件,按照原策略繼續比較Virtual DOM樹即可。
如果不是同一類型組件,則將該組件判定為dirty component,從而替換整個組件下所有子節點。
對于同一類型的組件,有可能其Virtual DOM沒有任何變化,如果能確切知道這點,那么可以節省大量diff運算時間。因此,react允許通過shouldComponentUpdate()判定該組件是否需要進行diff分析。
???????1、3兩條對于react的使用者來說非常好理解,一樣的組件,直接比較子組件的變化即可;開發者可以控制組件更新的條件,也可以對diff進行優化。
???????那么第2條可能就不是那么容易被理解了,為什么不是同一類型的組件就一定直接替換呢?比如我一個A組件一個B組件,都是一個div嵌套一個p標簽,里面有個span包裹的文字,兩個組件只有文字不同,那我直接替換組件豈不是浪費了div、p和span標簽的重新創建嗎?
???????這里react認為,兩個不同類型的組件,在實際處理業務過程中,結構一致的概率很低。因為既然拆分為兩個不同的組件,若里面的dom結構還非常相似,只能說明對組件的拆分粒度還不夠。不同組件在業務上完成的事情應該是不同的,所以不去考慮結構近似的情況,直接替換組件。
六、element diff???????我們介紹tree diff的時候有提到過,react在對比區別的時候是通過同一個父組件下的所有兄弟節點為一組進行新老對比的。這當中對比的細節才是整個diff算法最精粹的地方。
???????react對待同一層級的代碼只會進行三種操作,插入,刪除,移動。我們可以理解為原本沒有的組件,我們進行插入操作;新的變化之后消失的組件我們進行刪除操作;一個組件新舊變化之后,位置發生偏移,則使用移動操作。
???????具體的移動方案,大家可以看看大神的這篇知乎,里面講的更多例子https://zhuanlan.zhihu.com/p/...
???????而我直接在react這段的代碼中增加注釋,帶大家一起看看react diff的詳細邏輯
{ _updateChildren: function(nextNestedChildrenElements, transaction, context) { var prevChildren = this._renderedChildren; // 變更前的現有dom var nextChildren = this._reconcilerUpdateChildren( prevChildren, nextNestedChildrenElements, transaction, context ); // 待更新dom if (!nextChildren && !prevChildren) { // 二者有一個不存在,則退出方法 return; } var name; var lastIndex = 0; // 這個值非常關鍵,用來決定移動位置 var nextIndex = 0; // 節點指針,填充一個位置向后移一位 for (name in nextChildren) { // 開始遍歷新節點,與老節點做對比,這里的name可以理解為組件的key,唯一標志 if (!nextChildren.hasOwnProperty(name)) { continue; // porto上的屬性不要 } var prevChild = prevChildren && prevChildren[name]; var nextChild = nextChildren[name]; if (prevChild === nextChild) { //看看老節點上面有沒有相同的節點,如果存在就不再新建了 // 移動節點 this.moveChild(prevChild, nextIndex, lastIndex); lastIndex = Math.max(prevChild._mountIndex, lastIndex);//新的移動下標,移動的時候老節點和移動下標,哪個大取哪個 prevChild._mountIndex = nextIndex; // 老節點下標直接移動到現有下標位置 } else { // 老集合里面有相同名稱節點,但是內容不同了,直接把老的刪了,新建一個 if (prevChild) { lastIndex = Math.max(prevChild._mountIndex, lastIndex); // 刪除節點 this._unmountChild(prevChild); } // 初始化并創建節點 this._mountChildAtIndex( nextChild, nextIndex, transaction, context ); } nextIndex++; } for (name in prevChildren) { // 全部都遍歷完了,老節點中剩余的節點刪除掉 if (prevChildren.hasOwnProperty(name) && !(nextChildren && nextChildren.hasOwnProperty(name))) { this._unmountChild(prevChildren[name]); } } this._renderedChildren = nextChildren; // 新節點完成 }, // 移動節點 moveChild: function(child, toIndex, lastIndex) { if (child._mountIndex < lastIndex) { // 如果當前新節點下標小于已移動下標,則不用移動 this.prepareToManageChildren(); enqueueMove(this, child._mountIndex, toIndex); } }, // 創建節點 createChild: function(child, mountImage) { this.prepareToManageChildren(); enqueueInsertMarkup(this, mountImage, child._mountIndex); }, // 刪除節點 removeChild: function(child) { this.prepareToManageChildren(); enqueueRemove(this, child._mountIndex); }, _unmountChild: function(child) { this.removeChild(child); child._mountIndex = null; }, _mountChildAtIndex: function( child, index, transaction, context) { var mountImage = ReactReconciler.mountComponent( child, transaction, this, this._nativeContainerInfo, context ); child._mountIndex = index; this.createChild(child, mountImage); }, }
???????就此,react虛擬dom和diff算法就介紹完了,讀懂上面的代碼,至少要知道,react中為什么盡量減少一個dom從最后移動到最前面的操作,就算是理解了。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/96434.html
摘要:市面上竟然擁有多個虛擬庫。虛擬庫,就是出來后的一種新式庫,以虛擬與算法為核心,屏蔽操作,操作數據即操作視圖。及其他虛擬庫已經將虛擬的生成交由與處理了,因此不同點是,虛擬的結構與算法。因此虛擬庫是分為兩大派系算法派與擬態派。 去哪兒網迷你React是年初立項的新作品,在這前,去哪兒網已經深耕多年,擁有QRN(react-native的公司制定版),HY(基于React的hybird方案)...
摘要:是一個最小的庫,但由于其對尺寸的追求,它的很多代碼可讀性比較差,市面上也很少有全面且詳細介紹的文章,本篇文章希望能幫助你學習的源碼。建議與源碼一起閱讀本文。 作為一名前端,我們需要深入學習react的運行機制,但是react源碼量已經相當龐大,從學習的角度,性價比不高,所以學習一個react mini庫是一個深入學習react的一個不錯的方法。 preact是一個最小的react mi...
摘要:的一個突出特點是擁有極速地渲染性能。該功能依靠的就是研發團隊弄出的虛擬機制以及其獨特的算法。在的算法下,在同一位置對比前后節點只要發現不同,就會刪除操作前的節點包括其子節點,替換為操作后的節點。 React的一個突出特點是擁有極速地渲染性能。該功能依靠的就是facebook研發團隊弄出的虛擬dom機制以及其獨特的diff算法。下面簡單解釋一下react虛擬dom機制和diff算法的實現...
摘要:很多人認為虛擬最大的優勢是算法,減少操作真實的帶來的性能消耗。雖然這一個虛擬帶來的一個優勢,但并不是全部?;氐阶铋_始的問題,虛擬到底是什么,說簡單點,就是一個普通的對象,包含了三個屬性。 是什么? 虛擬 DOM (Virtual DOM )這個概念相信大家都不陌生,從 React 到 Vue ,虛擬 DOM 為這兩個框架都帶來了跨平臺的能力(React-Native 和 Weex)。因...
閱讀 1335·2019-08-30 15:44
閱讀 1385·2019-08-29 18:42
閱讀 440·2019-08-29 13:59
閱讀 777·2019-08-28 17:58
閱讀 2819·2019-08-26 12:02
閱讀 2422·2019-08-23 18:40
閱讀 2411·2019-08-23 18:13
閱讀 3112·2019-08-23 16:27