天天看點

談談對虛拟DOM的了解

快速搞定虛拟 DOM 的兩個“大問題”

虛拟 DOM(Virtual DOM)​

​本質上是JS 和 DOM 之間的一個映射緩存​

​,它在形态上表現為一個能夠描述 DOM 結構及其屬性資訊的 JS 對象
談談對虛拟DOM的了解

就這個示例來說,你需要把握住以下兩點:

  • 虛拟 DOM 是 JS 對象
  • 虛拟 DOM 是對真實 DOM 的描述

我們看看 React 中的虛拟 DOM 大緻是如何工作的

  • ​挂載階段​,React 将結合 JSX 的描述,建構出虛拟 DOM 樹,然後通過​

    ​ReactDOM.render​

    ​ 實作虛拟 DOM 到真實 DOM 的映射(觸發渲染流水線);
  • ​更新階段​,頁面的變化在作用于真實 DOM 之前,會先作用于虛拟​

    ​DOM​

    ​​,虛拟​

    ​DOM​

    ​ 将在 JS 層借助算法先對比出具體有哪些真實 DOM 需要被改變,然後再将這些改變作用于真實 DOM。

虛拟 DOM 是如何解決問題的

談談對虛拟DOM的了解

而在虛拟 DOM 的加持下,事情變成了這樣:

談談對虛拟DOM的了解

注意圖中的“模闆”二字加了引号,這是因為虛拟 DOM 在實作上并不總是借助模闆。比如 React 就使用了 JSX,前面咱們着重講過,JSX 本質不是模闆,而是一種使用體驗和模闆相似的 JS 文法糖

​差別就在于多出了一層虛拟 DOM 作為緩沖層​

​​。這個緩沖層帶來的利好是:​

​當 DOM 操作(渲染更新)比較頻繁時,它會先将前後兩次的虛拟 DOM 樹進行對比,定位出具體需要更新的部分,生成一個“更新檔集”,最後隻把“更新檔”打在需要更新的那部分真實 DOM 上,實作精準的“差量更新”​

​。這個過程對應的虛拟 DOM 工作流如下圖所示:
談談對虛拟DOM的了解
注:圖中的 ​

​diff 和 patch​

​ 其實都是函數名,這些函數取材于一個獨立的虛拟 DOM 庫

還需要說明的一點是, 虛拟 DOM 和 Redux 一樣,不依附于任何具體的架構。學習虛拟 DOM,實際上可以完全不借助 React;但學習 React,就必須了解虛拟 DOM

React 選用虛拟 DOM,真的是為了更好的性能嗎?

在整個 DOM 操作的演化過程中,主要沖突并不在于性能,而在于開發者寫得爽不爽,在于研發體驗/研發效率。​

​虛拟 DOM 不是别的,正是前端開發們為了追求更好的研發體驗和研發效率而創造出來的高階産物​

​。

虛拟 DOM 并不一定會帶來更好的性能,React 官方也從來沒有把虛拟 DOM 作為性能層面的賣點對外輸出過。​

​虛拟 DOM 的優越之處在于,它能夠在提供更爽、更高效的研發模式(也就是函數式的 UI 程式設計方式)的同時,仍然保持一個還不錯的性能​

性能問題屬于前端領域複雜度比較高的問題。當我們量化性能的時候,往往并不能隻追求一個單一的資料,而是需要結合具體的參照物、渲染的階段、資料的吞吐量等各種要素來作分情況的讨論。

拿前面講過的模闆渲染來舉例,我們可以對比一下它和虛拟 DOM 在性能開銷上的差異。兩者的渲染工作流對比如下圖所示:

談談對虛拟DOM的了解

從圖中可以看出,模闆渲染的步驟1,和虛拟 DOM 渲染的步驟1、2都屬于 JS 範疇的行為,這兩者是具備可比性的,我們放在一起來看:動态生成 HTML 字元串的過程本質是對字元串的拼接,對性能的消耗是有限的;而虛拟 DOM 的建構和 diff 過程邏輯則相對複雜,它不可避免地涉及遞歸、周遊等耗時操作。是以在 JS 行為這個層面,模闆渲染勝出

模闆渲染的步驟3,和虛拟 DOM 的步驟3 都屬于 DOM 範疇的行為,兩者具備可比性,是以我們仍然可以愉快地對比下去:​

​模闆渲染是全量更新,而虛拟 DOM 是差量更新​

​。

乍一看好像差量更新一定比全量更新高效,但你需要考慮這樣一種情況:資料内容變化非常大(或者說整個發生了改變),促使差量更新計算出來的結果和全量更新極為接近(或者說完全一樣)。

在這種情況下,DOM 更新的工作量基本一緻,而虛拟 DOM 卻伴随着開銷更大的 JS 計算,此時會出現的一種現象就是模闆渲染和虛拟 DOM 在整體性能上難分伯仲:若兩者最終計算出的 DOM 更新内容完全一緻,那麼虛拟 DOM 大機率不敵模闆渲染;但隻要兩者在最終 DOM 操作量上拉開那麼一點點的差距,虛拟 DOM 就将具備戰勝模闆渲染的底氣。​

​因為虛拟 DOM 的劣勢主要在于 JS 計算的耗時,而 DOM 操作的能耗和 JS 計算的能耗根本不在一個量級​

​,極少量的 DOM 操作耗費的性能足以支撐大量的 JS 計算。

當然,上面讨論的這種情況相對來說比較極端。在實際的開發中,更加高頻的場景是這樣的:我每次 setState 的時候隻修改少量的資料,比如一個對象中的某幾個屬性,再比如一個數組中的某幾個元素。在這樣的場景下,模闆渲染和虛拟 DOM 之間 DOM 操作量級的差距就完全拉開了,虛拟 DOM 将在性能上具備絕對的優勢。

注意,此處的結論是“在 XXX 場景下,虛拟 DOM 相對于 XXX 具備性能優勢”,它是有嚴格限定條件的。有人不到黃河心不死,可能又要問“那虛拟 DOM 對比 jQuery 呢?”“那虛拟 DOM 對比原生 DOM 呢?”。我想說的是,性能問題不能一概而論,而且咱都講到這個份上了,就不要再鑽性能這個牛角尖了。jQuery、原生 DOM 在思維模式上來說和虛拟 DOM 截然不同,強行比較意義不大。

前面又是分析又是舉例地說了這麼多,其實我最終希望你明白的事情隻有一件:​

​虛拟 DOM 的價值不在性能,而在别處​

​。是以想要從性能角度來把握虛拟 DOM 的優勢,無異于南轅北轍。偏偏在面試場景下,10 個人裡面有 9 個都走這條歧路,最後9個人裡面自然沒有一個能自圓其說,實在讓人惋惜。

那麼虛拟 DOM 的價值到底是什麼呢?

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

  • ​研發體驗/研發效率的問題​

    ​:DOM 操作模式的每一次革新,背後都是前端對效率和體驗的進一步追求。虛拟 DOM 的出現,為資料驅動視圖這一思想提供了高度可用的載體,使得前端開發能夠基于函數式 UI 的程式設計方式實作高效的聲明式程式設計。
  • ​跨平台的問題​

    ​:虛拟 DOM 是對真實渲染内容的一層抽象。若沒有這一層抽象,那麼視圖層将和渲染平台緊密耦合在一起,為了描述同樣的視圖内容,你可能要分别在 Web 端和 Native 端寫完全不同的兩套甚至多套代碼。但現在中間多了一層描述性的虛拟 DOM,它描述的東西可以是真實 DOM,也可以是iOS 界面、安卓界面、小程式......同一套虛拟 DOM,可以對接不同平台的渲染邏輯,進而實作“一次編碼,多端運作”,如下圖所示。其實說到底,跨平台也是研發提效的一種手段,它在思想上和1是高度呼應的。
談談對虛拟DOM的了解

除了差量更新以外,“批量更新”也是虛拟 ​

​DOM​

​​ 在性能方面所做的一個重要努力:“批量更新”在通用虛拟 DOM 庫裡是由 ​

​batch​

​​ 函數來處理的。在差量更新速度非常快的情況下(比如極短的時間裡多次操作同一個 ​

​DOM​

​),使用者實際上隻能看到最後一次更新的效果。這種場景下,前面幾次的更新動作雖然意義不大,但都會觸發重渲染流程,帶來大量不必要的高耗能操作

這時就需要請 ​

​batch​

​​ 來幫忙了,​

​batch​

​​ 的作用是緩沖每次生成的更新檔集,它會把收集到的多個更新檔集暫存到隊列中,再将最終的結果交給渲染函數,最終實作集中化的 ​

​DOM​

​ 批量更新