從新認識 Virtual DOM

那個爭議開端

這件事還要從 2013 年那個秋天提及。javascript

這實際上很是快,主要是由於大多數DOM操做每每很慢。DOM上有不少性能工做,但大多數DOM操做都會丟幀。html

2013 React Pete Hunt

對!就是這張圖,這張圖把你們引入了 DOM 操做是昂貴且慢的,Virtual DOM 是快速的思惟裏。前端

6 年後的今天,React 已經風靡全球,Virtual DOM 也受到了你們的承認,國產之星 VUE 也使用了 Virtual DOMjava

那麼問題來了,Virtual DOM 真的快嗎?Virtual DOM 的意義究竟是什麼?咱們爲何要使用 Virtual DOMreact

咱們都據說直接更新文檔對象模型(DOM)效率低且速度慢。可是,咱們中不多有人真的有數據支持它。關於React虛擬DOM的討論是,它是一種更有效的方式來更新 Web 應用程序中的視圖,但咱們不多有人知道爲何以及這種效率是否會致使更快的頁面渲染時間。git

拋開使用 React 的其餘好處,例如單向數據綁定和組件,我將討論 Virtual DOM 究竟是什麼,以及它是否可以證實 React 比其餘UI庫更合理(或者根本沒有UI庫) 。github

咱們爲何須要 UI 庫

咱們爲何須要 UI 庫呢?算法

我敢確定,如今的前端界,很大一部分人離開了三大框架以後就不知道該怎麼辦了,他們可能理所固然的認爲視圖和數據是綁定的(VUE),或者直接使用 setState 來更新視圖(React)。npm

有了 UI 庫以後,咱們能夠直接數據與視圖綁定,而不須要再操做 DOM瀏覽器

咱們爲何不想操做 DOM

這裏不會詳細的講 DOM,只會粗略帶過。

DOM表明文檔對象模型,是結構化文本的抽象。對於Web開發人員,此文本是HTML代碼,DOM簡稱爲*HTML DOMHTML元素成爲DOM中的節點*。

HTML DOM提供了一個用於遍歷和修改節點的接口(API)。它包含像getElementById或的方法removeChild。咱們一般使用JavaScript語言來處理DOM,由於......好吧,沒人知道爲何:)。

所以,每當咱們想要動態地更改網頁的內容時,咱們都會修改DOM

var item = document.getElementById("myLI");
item.parentNode.removeChild(item);
複製代碼

document是根節點的抽象getElementByIdparentNode並且removeChild是來自HTML DOM API的方法。

那麼問題來了,因爲HTML DOM始終是樹形結構,咱們能夠很容易地遍歷每一個節點,可是現在Web APP的當下,DOM樹愈來愈大,咱們須要不停的修改大量的DOM樹。這是真正使人痛苦的地方。

咱們一般是如下一個流程來更新 DOM

  1. 遍歷(或者使用 id)樹找到相關的節點
  2. 在有必要時更新這個節點

這明顯有幾個問題:

  1. 很難管理。找一個節點並分析上下文的關係,耗時耗力,一不當心喜提bug
  2. 效率極低。

爲何更新 DOM 很慢

更新DOM並不慢,就像更新任何JavaScript對象同樣。

那到底是什麼讓更新真正的DOM變慢?

是繪製。

佈局過程當中,繪製佔用了大部分時間。

結合下圖,以及此文章,你會明白,更新 DOM 的真正問題是屏幕的繪製。

img

負責在瀏覽器屏幕上顯示或呈現網頁的渲染引擎解析HTML頁面以建立DOM。它還解析CSS並將CSS應用於HTML,從而建立渲染樹,此過程稱爲**attachment**。

因此,當咱們這樣作時

document.getElementById('elementId').innerHTML="New Value"
複製代碼

發生如下事情:

  1. 瀏覽器必須解析HTML
  2. 它刪除了elementId 的子元素
  3. 使用"New Value"更新DOM
  4. 從新計算父和子的CSS
  5. 更新佈局,即每一個元素在屏幕上的精確座標
  6. 遍歷渲染樹並在瀏覽器顯示上繪製它

從新計算CSS和更改佈局使用複雜的算法,它們會影響性能。

所以,更新真正的DOM並不只僅涉及更新DOM,而是涉及許多其餘過程。

此外,上述每一個步驟都針對真實DOM的每次更新運行,即若是咱們更新真實DOM 10次,則上述步驟中的每個將重複10次。這就是爲何更新 DOM 很慢的緣由。

神奇的 Virtual DOM

首先 , Virtual DOM不是由 React發明的,但React使用它並免費提供。

因爲 DOM 操做的複雜性,Virtual DOM被創造了出來,他以一個虛擬樹的狀態,存儲在內存中,再映射到真實的 DOM,每次更新,都是虛擬樹的對比,再將差別部分進行更新,並反映到真實 DOM 上去,這樣咱們就減小了對真實 DOM 的操做。

React中更新虛擬DOM的速度更快,由於React使用了

  1. 高效的diff算法
  2. 批量batching操做
  3. 僅有效地更新子樹
  4. 使用可觀察(observable)而不是髒檢查來檢測更改

AngularJS使用髒檢查來查找已更改的模型。這個髒檢查過程在指定時間後循環運行。隨着應用程序的增加,檢查整個模型會下降性能,從而使應用程序變慢。

每當調用setState()方法時,ReactJS都會從頭開始建立整個Virtual DOM。建立整棵樹很是快,所以不會影響性能。

在任何給定時間,ReactJS維護兩個Virtual DOM,一個具備更新的狀態Virtual DOM,另外一個具備先前的狀態Virtual DOM

使用diff算法比較Virtual DOM以找到並更新至Real DOM

讓咱們舉個栗子,首先咱們 state.subject 的值是 world

<div>
  <div id="header">
    <h1>Hello, {{state.subject}}!</h1>
    <p>How are you today?</p>
  </div>
</div>
複製代碼

解析後的 Virtual DOM 能夠表示爲:

{
  tag: 'div',
  children: [
    {
      tag: 'div',
      attributes: {
        id: 'header'
      },
      children: [
        {
          tag: 'h1',
          children: 'Hello, World!'
        },
        {
          tag: 'p',
          children: 'How are you today?'
        }
      ]
    }
  ]
}
複製代碼

如今, state.subject的值改變爲Mom,那麼渲染出來的 Virtual DOM爲:

{
  tag: 'div',
  children: [
    {
      tag: 'div',
      attributes: {
        id: 'header'
      },
      children: [
        {
          tag: 'h1',
          children: 'Hello, Mom!'
        },
        {
          tag: 'p',
          children: 'How are you today?'
        }
      ]
    }
  ]
}
複製代碼

經過 diff 算法以後,肯定只更新的了 h1 這個元素,那麼將更新的元素再映射到 DOM 上即完成了這次的更新。

至於 batchingdiff 算法 ,內容量較大,須要另開一篇博客講,目前能搜到的講解也很多,你們能夠去搜搜。

Virtual DOM 真的快過直接操做 DOM 嗎

關於Virtual DOM的每一篇文章和文章都會指出,雖然今天的JavaScript引擎速度很是快,但讀取和寫入瀏覽器的DOM的速度很慢。

這不徹底正確。DOM很快。添加和刪除DOM節點並不比在JavaScript對象上設置屬性慢得多。這只是一個簡單的操做。

例以下面一個例子:

這是一個使用原生 DOM 渲染的方式:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>Hello JavaScript!</title>

</head>
<body>
<div id="example"></div>
<script> document.getElementById("example").innerHTML = "<h1>Hello, world!</h1>"; </script>
</body>
</html>
複製代碼

這是一個使用 React 實現的方式:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
    <script src="build/react.js"></script>
    <script src="build/react-dom.js"></script>
</script>
</head>
<body>
<div id="example"></div>
<script type="text/babel"> ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('example') ); </script>
</body>
</html>
複製代碼

使用原生須要渲染的時間:

Load Graph

使用 React 須要渲染的時間:

Load Graph

咋一看,原生渲染速度大大快於 React

可是咱們忽略了一個問題,就是頁面數據量不多,這樣操做,在一個大型列表全部數據都變了的狀況下,還算是合理,可是,當只有一行數據發生變化時,它也須要重置整個 innerHTML,這時候顯然就形成了大量浪費。

比較 innerHTMLVirtual DOM 的重繪過程以下:

  • innerHTML: render html string ==> 從新建立全部 DOM 元素
  • Virtual DOM: render Virtual DOM ==> diff ==> 必要的 DOM 更新

DOM 操做比起來,js 計算是很是廉價的。Virtual DOM render + diff 顯然比渲染 html 字符串要慢,可是,它依然是純 js 層面的計算,比起後面的 DOM 操做來講,依然好了太多。

瀏覽器在DOM更改時必須執行的佈局。每次DOM更改時,瀏覽器都須要從新計算CSS,進行佈局並從新繪製網頁,這須要大量時間。

瀏覽器製造商不斷努力縮短從新繪製屏幕所需的時間,能夠作的最大的事情是最小化和批量DOM更改。

這種減小和批處理DOM更改的策略,採用另外一個抽象級別,是ReactVirtual DOM背後的理念。

最後

React 歷來沒有說過 「React 比原生操做 DOM 快」。React給咱們的保證是,在不須要手動優化的狀況下,它依然能夠給咱們提供過得去的性能。

React掩蓋了底層的 DOM 操做,能夠用更聲明式的方式來描述咱們目的,從而讓代碼更容易維護。

借鑑了知乎上的回答:沒有任何框架能夠比純手動的優化 DOM 操做更快,由於框架的 DOM 操做層須要應對任何上層 API 可能產生的操做,它的實現必須是普適的。針對任何一個 benchmark,我均可以寫出比任何框架更快的手動優化,可是那有什麼意義呢?在構建一個實際應用的時候,你難道爲每個地方都去作手動優化嗎?出於可維護性的考慮,這顯然不可能。

最後推廣一下我基於 Taro 框架寫的組件庫:MP-ColorUI

能夠順手 star 一下我就很開心啦,謝謝你們。

點這裏是文檔

點這裏是 GitHub 地址

相關文章
相關標籤/搜索