React中的虛擬DOM詳解
React中的虛擬DOM詳解
在 React 還沒出現前的前端開發,我們需要直接操作DOM結構,操作既複雜,兼容性又差,當我們有了jQuery之後,透過jQuery封裝的API,我們能夠更方便的操作DOM,但是DOM是很慢的,其元素非常巨大,頁面的性能問題常常會因為操作DOM而出現瓶頸,於是乎有什麼方法可以讓操作DOM的效率提升呢? ReactJs 提出了虛擬DOM的概念
核心思想
1 | 1. 對複雜的DOM結構,提供一種方便操作的工作,使得開發效率提升 |
假設今天當 ReactJs 沒有虛擬DOM的實現,我們必須要自己做,我們應該用什麼想法去設計這樣的功能呢?
- 先有數據,透過State存放數據
- JSX 模板,即Component中的 Render 函數
- 數據 + 模板結合,生成真實DOM,來顯示 :
- React Render 函式中,會將數據跟模板相結合生成一個真實的DOM,並掛載在頁面上。
- State 發生改變
- 假設以ReactJS的流程來看,又會執行一次步驟三
這樣其實簡單的實現思路就達成了,但是上面的流程有什麼樣的缺陷呢?
- 第一次生成一個完整的DOM片段。
- 第二次生成一個完整的DOM片段。
- 第二次的DOM 替換第一次的DOM,非常耗性能。
假設我們就上述思路去思考,有什麼方式或者流程可以優化的呢? 其實我們只要將變動的DOM做差異比對,更新局部有更新的DOM元素即可,修改後的思路如下 :
- State 數據
- JSX 模板
- 數據 + 模板結合,生成真實DOM,來顯示
- State 數據發生改變
- 數據 + 模板結合,生成真實的DOM,並不直接替換原始的DOM
- 新的DOM(DocumentFragment)和原始的DOM做比對,找差異
- 找出變化的元素
- 只用新的DOM中的有更新的元素,替換掉老的DOM中的既有元素
但上述的思路還有什麼缺陷呢 ? 雖然跟第一個方法比肯定有性能上的提升,但整體的性能的提升並不明顯。因為我們還是去操作原生DOM元素,在一開始文章開頭我們有提到,DOM的操作其實很慢,在上面的兩種方法中,其實還是真正的去操作了DOM元素,那麼我們就需要更近一步的思考了。
React JS 的解決思路
由於操作DOM元素很慢,所以ReactJS 想到了一個方法,如果我們將DOM的結構用 Javascript 的對象儲存,並在比對差異的時候是針對 Javascript 的對象操作(虛擬DOM),所以我們去想一下 ReactJS 是怎麼實現的呢
- state數據
- JSX模板
- 數據 + 模板結合,生成真實的DOM來顯示
1
<div id ='abc'><span>hello wordl</span></div>
- 生成虛擬DOM
1
['div', {id: 'abc'}, ['span', {}, 'helloworld']]
- State 發生變化
- 數據 + 模板 生成新的虛擬DOM
1
['div', {id: 'abc'}, ['span', {}, 'bye bye']]
- 比較原始虛擬DOM和新的虛擬DOM的區別,找到區別是span中內容。
- 透過高效率的Diff算法,這邊不做太詳細的描述,因為DOM其實是一個樹狀結構,在React中,如果一個父節點底下有一個子節點的數據需要做改變,那麼這整個父節點之下的所有元素都會被記錄成有差異變化
- 透過高效率的Diff算法,這邊不做太詳細的描述,因為DOM其實是一個樹狀結構,在React中,如果一個父節點底下有一個子節點的數據需要做改變,那麼這整個父節點之下的所有元素都會被記錄成有差異變化
- 直接操作dom,改變span中的內容。
那在上面的實現方式中,跟我們一開始如果不採用虛擬DOM相比,性能比又是如何呢? 其實我們在第四步的過程中,生成虛擬DOM會相對消耗一些性能,但是由於虛擬DOM是JS對象,JS對象的生成的性能消耗非常小,所以這一段我們基本可以忽略他,但是在第六步跟第七步,我們可以極大的提升性能,為什麼呢?因為我們在改變數據的同時,不需要再去生成相對複雜的DOM元素,而是一個JS對象,然後我們再比對差異的時候,也是透過JS對象的比對,去找到需要改變的元素,在這樣子的操作下,ReactJS 滿足了我們一開始所說的目的,既可以提高性能,也可以透過簡單的JSX模板的方式,去簡單的操作我們的DOM元素。