React中的虛擬DOM詳解

React中的虛擬DOM詳解

在 React 還沒出現前的前端開發,我們需要直接操作DOM結構,操作既複雜,兼容性又差,當我們有了jQuery之後,透過jQuery封裝的API,我們能夠更方便的操作DOM,但是DOM是很慢的,其元素非常巨大,頁面的性能問題常常會因為操作DOM而出現瓶頸,於是乎有什麼方法可以讓操作DOM的效率提升呢? ReactJs 提出了虛擬DOM的概念

核心思想

1
2
1. 對複雜的DOM結構,提供一種方便操作的工作,使得開發效率提升
2. 保證最小化的DOM操作,使得執行效率提升

假設今天當 ReactJs 沒有虛擬DOM的實現,我們必須要自己做,我們應該用什麼想法去設計這樣的功能呢?

  1. 先有數據,透過State存放數據
  2. JSX 模板,即Component中的 Render 函數
  3. 數據 + 模板結合,生成真實DOM,來顯示 :
    • React Render 函式中,會將數據跟模板相結合生成一個真實的DOM,並掛載在頁面上。
  4. State 發生改變
  5. 假設以ReactJS的流程來看,又會執行一次步驟三

這樣其實簡單的實現思路就達成了,但是上面的流程有什麼樣的缺陷呢?

  • 第一次生成一個完整的DOM片段。
  • 第二次生成一個完整的DOM片段。
  • 第二次的DOM 替換第一次的DOM,非常耗性能。

假設我們就上述思路去思考,有什麼方式或者流程可以優化的呢? 其實我們只要將變動的DOM做差異比對,更新局部有更新的DOM元素即可,修改後的思路如下 :

  1. State 數據
  2. JSX 模板
  3. 數據 + 模板結合,生成真實DOM,來顯示
  4. State 數據發生改變
  5. 數據 + 模板結合,生成真實的DOM,並不直接替換原始的DOM
  6. 新的DOM(DocumentFragment)和原始的DOM做比對,找差異
  7. 找出變化的元素
  8. 只用新的DOM中的有更新的元素,替換掉老的DOM中的既有元素

但上述的思路還有什麼缺陷呢 ? 雖然跟第一個方法比肯定有性能上的提升,但整體的性能的提升並不明顯。因為我們還是去操作原生DOM元素,在一開始文章開頭我們有提到,DOM的操作其實很慢,在上面的兩種方法中,其實還是真正的去操作了DOM元素,那麼我們就需要更近一步的思考了。

React JS 的解決思路

由於操作DOM元素很慢,所以ReactJS 想到了一個方法,如果我們將DOM的結構用 Javascript 的對象儲存,並在比對差異的時候是針對 Javascript 的對象操作(虛擬DOM),所以我們去想一下 ReactJS 是怎麼實現的呢

  1. state數據
  2. JSX模板
  3. 數據 + 模板結合,生成真實的DOM來顯示
    1
    <div id ='abc'><span>hello wordl</span></div>
  4. 生成虛擬DOM
    1
    ['div', {id: 'abc'}, ['span', {}, 'helloworld']]
  5. State 發生變化
  6. 數據 + 模板 生成新的虛擬DOM
    1
    ['div', {id: 'abc'}, ['span', {}, 'bye bye']]
  7. 比較原始虛擬DOM和新的虛擬DOM的區別,找到區別是span中內容。
    • 透過高效率的Diff算法,這邊不做太詳細的描述,因為DOM其實是一個樹狀結構,在React中,如果一個父節點底下有一個子節點的數據需要做改變,那麼這整個父節點之下的所有元素都會被記錄成有差異變化
      alt
  8. 直接操作dom,改變span中的內容。

那在上面的實現方式中,跟我們一開始如果不採用虛擬DOM相比,性能比又是如何呢? 其實我們在第四步的過程中,生成虛擬DOM會相對消耗一些性能,但是由於虛擬DOM是JS對象,JS對象的生成的性能消耗非常小,所以這一段我們基本可以忽略他,但是在第六步跟第七步,我們可以極大的提升性能,為什麼呢?因為我們在改變數據的同時,不需要再去生成相對複雜的DOM元素,而是一個JS對象,然後我們再比對差異的時候,也是透過JS對象的比對,去找到需要改變的元素,在這樣子的操作下,ReactJS 滿足了我們一開始所說的目的,既可以提高性能,也可以透過簡單的JSX模板的方式,去簡單的操作我們的DOM元素。

參考 :
https://foio.github.io/virtual-dom/