[react] 什麼是虛擬dom?虛擬dom比操作原生dom要快嗎?虛擬dom是如何轉變成真實dom並渲染到頁面的?

2022-06-09 21:01:17

壹 ❀ 引

虛擬DOM(Virtual DOM)在前端領域也算是老生常談的話題了,若你瞭解過vue或者react一定避不開這個話題,因此虛擬DOM也算是面試中常問的一個點,那麼通過本文,你將瞭解到如下幾點:

  • 虛擬DOM究竟是什麼?
  • 虛擬DOM的優勢是什麼?解決了什麼問題?
  • 虛擬DOM的效能比操作原生DOM要快嗎?
  • react中的虛擬DOM是如何生成的?
  • react是如何將虛擬DOM轉變成真實dom的?

閱讀前建議與提醒:

  • 本篇文章可能比較長,建議挑一個空閒的時間段閱讀,還請保持耐心,我將以通俗易懂的口吻帶你瞭解這些問題。
  • 本文原始碼分析部分react版本為17.0.2,無須擔心低版本原始碼分析對你之後面試幫助不大的問題。
  • 如果可以,泡上一杯性溫的茶或者咖啡,保持一個舒服的姿勢會讓你閱讀更加愉快。

那麼本文開始。

貳 ❀ 在虛擬dom之前

在聊虛擬DOM之前,我還是想先聊聊在沒有虛擬DOM概念的時候,我們是如何更新頁面的,所以在這裡我將先引出前端框架(庫)的發展史,通過這個變遷過程也便於大家理解虛擬dom的出現到底解決了什麼問題。

貳 ❀ 壹 石器時代jqery

其實在15年以及更早之前,前端面試涉及到效能優化問題,往往都會提到儘可能少的操作DOM這一點。為什麼呢?因為在原生JS的年代,前端專案檔案都明確分為html、jscss三種,我們在js中獲取DOM,併為其繫結事件,通過事件監聽感知使用者在UI層的操作,並隨之更新DOM,從而達到頁面互動的目的:

而在後面,jqery的出現極大簡化了開發者操作DOM的成本,抹平了當時不同瀏覽器操作DOMAPI差異,為當時苦於ie以及不同瀏覽器自研API的開發者解決了不少相容性問題,當然JQ也並未改變開發者在JS層直接操作DOM這一現狀。

那麼我們為什麼說要儘可能少的操作DOM呢,這裡就涉及到重繪與迴流兩個概念,比如單純修改顏色就會引發重繪,刪除或新增一個DOM節點就會引發迴流和重繪,使用者雖然無法感知這個過程,但對於瀏覽器而言也存在消耗效能。所以針對於迴流,在此之後又提出了DocumentFragment檔案物件以優化多次操作DOM的方案。簡單理解就是,假如我要依次替換五個li節點,那麼我們可以建立一個DocumentFragment物件儲存這五個節點,然後一次性替換。

關於節流與重繪,若有興趣可讀讀博主頁面優化,談談重繪(repaint)和迴流(reflow)一文。

關於DocumentFragment可讀讀博主頁面優化,DocumentFragment物件詳解一文。

這些都是時代的眼淚,現在應該很少會有人提及,這裡就不再贅述了。

貳 ❀ 貳 青銅時代angularjs

JQ之後,angularjs(這裡指angularjs1而非angular)橫空出世,一招雙向繫結在當時更是驚為天人,除此之外,angularjs的模板語法也格外驚豔,我們將所有與資料掛鉤的節點通過{{}}包裹(vue在早期設計上大量借鑑了angularjs),比如:

<span>{{vm.name}}</span>

之後 view 檢視層就自動與 Model 資料層進行掛鉤(MVC那一套),只要 Model 層資料發生變化,view 層便自動更新。angularjs 的這種做法,徹底將開發者從操作 DOM 上解放了出來(為jq沒落埋下伏筆),自此之後開發者只用專注 Model 層的資料加工以及業務處理,至於頁面如何渲染全權交給 angularjs 底層處理即好了。

但需要注意的是,angularjs 在當時並沒有虛擬dom的概念,那它是怎麼做感知資料層變化以及更新檢視層的呢?angularjs有一套髒檢測機制$digesthtml中凡是使用了模板語法{{}}或者ng-bind指令的部分,都會被加入到髒檢測的warchers列表中,它是一個陣列,之後只要使用者通過ng-click(與傳統click不同,內建繫結了觸發髒檢測的機制)等方法改變了Model的資料,angularjs就會從頂層rootScope向下遞迴,依次存取每個子scope中的warchers列表,並對其中監聽的部分做新舊對比,如果不同則進行資料替換,以及DOM層的更新。

但是你要想想,一個應用那麼大的結構,只要某一個資料變化了就得從頂層向下對比N個子 scopewarchers 下的所有監聽物件,全量對比的效能有多差可想而知,angularjs 自身也意識到了這點,所以之後直接放棄了 angularjs 的維護轉而新開了 angular 專案。

對於 angularjs 髒檢測感興趣可以讀讀博主深入瞭解angularjs中的