怎麼理解虛擬 DOM?

一、前言

現在web前端的開發,對於MVVM框架的運用,那是信手拈來,用的飛起。一個xxx-cli工具,就能初始化一套模板,再填充業務程式碼,打包部署即可。但是會用,是一個方面,大家有沒有底層深入思考一下,這些框架核心的技術突破點在哪裡?解決了哪些問題?作為一個愛主動學習的童鞋,我們得花點小時間稍微去研究一下才行。今天,我們就簡單談談虛擬dom,來揭開它的神秘面紗。

首先看一道經典的面試題:

「為什麼我們需要虛擬 DOM?」。

這個問題比較常見的回答思路是:「DOM 操作是很慢的,而 JS 卻可以很快,直接操作 DOM 可能會導致頻繁的迴流與重繪,JS 不存在這些問題。因此虛擬 DOM 比原生 DOM 更快」

但實際真實這樣嗎?

二、虛擬dom是什麼?what。

虛擬 DOM(Virtual DOM)本質上是JS 和 DOM 之間的一個映射快取,它在形態上表現為一個能夠描述 DOM 結構及其屬性資訊的 JS 對象。它主要存儲在記憶體中。主要來說:

  • 1.虛擬dom是一個js對象
  • 2.虛擬dom能夠描述真實dom(存在一個對應關係)
  • 3.存儲在記憶體之中

以react為例,我們來看看虛擬dom到底長什麼樣子: 

三、為什麼會有虛擬dom?why。

首先,我們來看看遠古時代(可能就是在7,8年前),我們怎麼操作dom的。

1、什麼都不用,原生js

來看看原生js下我們怎麼查找元素的

// 根據類名查詢元素,返回一個滿足查詢條件數組
document.getElementsByClassName()

// 根據標籤名查詢元素,返回一個滿足查詢條件數組
document.getElementsByTagName()

// 根據標籤名查詢元素,返回一個滿足查詢條件結果
document.getElementById()

.....

來看看我們怎麼動態修改dom的

var displayName = 'hello,admin';
document.getElementById('#id').innerHtml = '<div>' + displayName+'</div>';

window.document.body.append('<div>hahahha</div>');

大家覺得這麼操作dom會高效嗎?

2、jquery時代

jQuery在操作dom時,對DOM API 封裝為了相對簡單和優雅的形式,同時一口氣做掉了跨瀏覽器的兼容工作,並且提供了鏈式 API 調用、插件擴展等一系列能力用於進一步解放生產力。最終達到的效果正是我們喜聞樂見的「寫得更少,做得更多」。

jQuery 使 DOM 操作變得簡單、快速,並且始終確保其形式穩定、可用性穩定。雖然現在看來並不完美,但是當年能夠一統江湖,確實在解放生產力,提高效率方面有極大的促進作用。

$('#id').show();
$('.className').hide().html('xxxxx');
3、模板引擎

我們以Handlebars這個模板為例

<!-- Include Handlebars from a CDN -->
<script src="//cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"></script>
<script>
  // compile the template
  var template = Handlebars.compile("Handlebars <b>{{doesWhat}}</b>");
  // execute the compiled template and print the output to the console
  console.log(template({ doesWhat: "rocks!" }));
 

</script>
{
  people: [
    "Yehuda Katz",
    "Alan Johnson",
    "Charles Jolley",
  ],
}
 <ul class="people_list">
  {{#each people}}
    <li>{{this}}</li>
  {{/each}}
</ul>

用模板引擎,來操作js,效率還是挺高的,爽歪歪啊。 每次數據發生變化時,我們都不用關心到底是哪裡的數據變了,也不用手動去點對點完成 DOM 的修改。只需要關注的僅僅是數據和數據變化本身,DOM 層面的改變模板引擎會幫我們做掉。

類似這種模板引擎,還有很多,我們可以對比一下其性能是怎樣的: 

模板引擎像極了一個只需要接收命令,就能夠把活幹得漂漂亮亮的「掃地機器人」!可惜的是,模板引擎出現的契機雖然是為了使用戶介面與業務數據相分離,但實際的應用場景基本局限在實現高效的字元串拼接這一個點上,因此不能指望它去做太複雜的事情。尤其令人無法接受的是,它在性能上的表現並不盡如人意:由於不夠「智慧」,它更新 DOM 的方式是將已經渲染出 DOM 整體註銷後再整體重渲染,並且不存在更新緩衝這一說。在 DOM 操作頻繁的場景下,模板引擎可能會直接導致頁面卡死。

也就是說,其實我們想要高效迅速的操作dom,同時還保證一個不錯的性能。修改了局部dom就替換局部。而不是整體全部替換。但是如果一直都是操作真實的dom,一直都是存在DOM 整體註銷後再整體重渲染的情況,那性能效率方面就大打折扣了。—-react團隊想了想,我們操作假的dom不就行了嗎?

因此 虛擬dom就自然而然的應運而生。

三、虛擬dom怎本工作的?how。

借用大神的一張圖來說明:

由此圖我們可以得出:

數據 + 模板 => 虛擬dom

這裡的模板,不一定是我們之前講的Handlebars這樣的js模板引擎。在react中,這種模板是以jsx的形勢存在。jsx 是Javascript 的一種語法擴展。你可以理解為在中react使用體驗和模板相似的 JS 語法糖。

相比於之前傳統的修改dom的方式,這種修改就是多了一個緩衝區。當 DOM 操作(渲染更新)比較頻繁時,它會先將前後兩次的虛擬 DOM 樹進行對比,定位出具體需要更新的部分,生成一個「修補程式集」,最後只把「修補程式」打在需要更新的那部分真實 DOM 上,實現精準的「差量更新」。 

需要注意的是,我們這裡為了方便講解虛擬dom,會藉助react舉例來說明,實際上,vue也支援虛擬dom。也就是說虛擬dom,是不依賴框架的。但是 react 主推虛擬dom,想更深入的掌握react,必須要了解虛擬dom。

在整個 DOM 操作的演化過程中,主要矛盾並不在於性能,在於研發體驗/研發效率(在於開發者寫得爽不爽)。虛擬 DOM 不是別的,正是前端開發們為了追求更好的研發體驗和研發效率而創造出來的高階產物。

虛擬 DOM 並不一定會帶來更好的性能,React 官方也從來沒有把虛擬 DOM 作為性能層面的賣點對外輸出過。虛擬 DOM 的優越之處在於,它能夠在提供更爽、更高效的研發模式(也就是函數式的 UI 編程方式)的同時,仍然保持一個還不錯的性能。

四、性能淺析對比

  • 動態生成 HTML 字元串的過程本質是對字元串的拼接,對性能的消耗是有限的;而虛擬 DOM 的構建和 diff 過程邏輯則相對複雜,它不可避免地涉及遞歸、遍歷等耗時操作。因此在 JS 行為這個層面,模板渲染勝出。

  • 模板和虛擬 DOM 在最後更新dom階段,都屬於 DOM 範疇的行為,兩者具備可比性,因此我們仍然可以愉快地對比下去:模板渲染是全量更新,而虛擬 DOM 是差量更新。

一般來說,更新一定比全量更新高效,但你需要考慮這樣一種情況:數據內容變化非常大(或者說整個發生了改變),促使差量更新計算出來的結果和全量更新極為接近(或者說完全一樣)。那麼虛擬dom在更新dom階段沒有性能優勢可言。

在這種情況下,DOM 更新的工作量基本一致,而虛擬 DOM 卻伴隨著開銷更大的 JS 計算,此時會出現的一種現象就是模板渲染和虛擬 DOM 在整體性能上難分伯仲:若兩者最終計算出的 DOM 更新內容完全一致,那麼虛擬 DOM 大概率不敵模板渲染;但只要兩者在最終 DOM 操作量上拉開那麼一點點的差距,虛擬 DOM 就將具備戰勝模板渲染的底氣。因為虛擬 DOM 的劣勢主要在於 JS 計算的耗時,而 DOM 操作的能耗和 JS 計算的能耗根本不在一個量級,極少量的 DOM 操作耗費的性能足以支撐大量的 JS 計算。

在實際的開發中,更加高頻的場景是這樣的:我每次 setState 的時候只修改少量的數據,比如一個對象中的某幾個屬性,再比如一個數組中的某幾個元素。在這樣的場景下,模板渲染和虛擬 DOM 之間 DOM 操作量級的差距就完全拉開了,虛擬 DOM 將在性能上具備絕對的優勢。

同時,我們考慮這種情況:短時間內對3種狀態進行修改,有虛擬dom這個緩衝,react可以將其加入到批量更新的任務中去。而不是每次不停的立即更新真是dom。

五、虛擬dom的意義

回到最開始的問題:為什麼我們需要虛擬dom?

在整個 DOM 操作的演化過程中,主要矛盾並不在於性能,而在於開發者寫得爽不爽,在於研發體驗/研發效率。虛擬 DOM 不是別的,正是前端開發們為了追求更好的研發體驗和研發效率而創造出來的高階產物。虛擬 DOM 並不一定會帶來更好的性能,React 官方也從來沒有把虛擬 DOM 作為性能層面的賣點對外輸出過。虛擬 DOM 的優越之處在於,它能夠在提供更爽、更高效的研發模式(也就是函數式的 UI 編程方式)的同時,仍然保持一個還不錯的性能。

虛擬 DOM 解決的關鍵問題有以下兩個。

  • 1、研發體驗/研發效率的問題:這一點前面已經反覆強調過,DOM 操作模式的每一次革新,背後都是前端對效率和體驗的進一步追求。虛擬 DOM 的出現,為數據驅動視圖這一思想提供了高度可用的載體,使得前端開發能夠基於函數式 UI 的編程方式實現高效的聲明式編程。

  • 2、跨平台的問題:虛擬 DOM 是對真實渲染內容的一層抽象。若沒有這一層抽象,那麼視圖層將和渲染平台緊密耦合在一起,為了描述同樣的視圖內容,你可能要分別在 Web 端和 Native 端寫完全不同的兩套甚至多套程式碼。但現在中間多了一層描述性的虛擬 DOM,它描述的東西可以是真實 DOM,也可以是iOS 介面、Android介面、小程式……同一套虛擬 DOM,可以對接不同平台的渲染邏輯,從而實現「一次編碼,多端運行」,

歸根結底,解決跨平台的問題,也是提高研發效率。