虛擬DOM
(Virtual DOM)在前端領域也算是老生常談的話題了,若你瞭解過vue
或者react
一定避不開這個話題,因此虛擬DOM
也算是面試中常問的一個點,那麼通過本文,你將瞭解到如下幾點:
DOM
究竟是什麼?DOM
的優勢是什麼?解決了什麼問題?DOM
的效能比操作原生DOM
要快嗎?react
中的虛擬DOM
是如何生成的?react
是如何將虛擬DOM
轉變成真實dom
的?閱讀前建議與提醒:
react
版本為17.0.2
,無須擔心低版本原始碼分析對你之後面試幫助不大的問題。那麼本文開始。
在聊虛擬DOM
之前,我還是想先聊聊在沒有虛擬DOM
概念的時候,我們是如何更新頁面的,所以在這裡我將先引出前端框架(庫)的發展史,通過這個變遷過程也便於大家理解虛擬dom的出現到底解決了什麼問題。
其實在15年以及更早之前,前端面試涉及到效能優化問題,往往都會提到儘可能少的操作DOM
這一點。為什麼呢?因為在原生JS的年代,前端專案檔案都明確分為html、js
與css
三種,我們在js
中獲取DOM
,併為其繫結事件,通過事件監聽感知使用者在UI層的操作,並隨之更新DOM
,從而達到頁面互動的目的:
而在後面,jqery
的出現極大簡化了開發者操作DOM
的成本,抹平了當時不同瀏覽器操作DOM
的API
差異,為當時苦於ie
以及不同瀏覽器自研API
的開發者解決了不少相容性問題,當然JQ
也並未改變開發者在JS
層直接操作DOM
這一現狀。
那麼我們為什麼說要儘可能少的操作DOM
呢,這裡就涉及到重繪與迴流兩個概念,比如單純修改顏色就會引發重繪,刪除或新增一個DOM
節點就會引發迴流和重繪,使用者雖然無法感知這個過程,但對於瀏覽器而言也存在消耗效能。所以針對於迴流,在此之後又提出了DocumentFragment
檔案物件以優化多次操作DOM
的方案。簡單理解就是,假如我要依次替換五個li
節點,那麼我們可以建立一個DocumentFragment
物件儲存這五個節點,然後一次性替換。
關於節流與重繪,若有興趣可讀讀博主頁面優化,談談重繪(repaint)和迴流(reflow)一文。
關於DocumentFragment
可讀讀博主頁面優化,DocumentFragment物件詳解一文。
這些都是時代的眼淚,現在應該很少會有人提及,這裡就不再贅述了。
在JQ
之後,angularjs
(這裡指angularjs1而非angular)橫空出世,一招雙向繫結在當時更是驚為天人,除此之外,angularjs
的模板語法也格外驚豔,我們將所有與資料掛鉤的節點通過{{}}
包裹(vue在早期設計上大量借鑑了angularjs),比如:
<span>{{vm.name}}</span>
之後 view
檢視層就自動與 Model
資料層進行掛鉤(MVC那一套),只要 Model
層資料發生變化,view
層便自動更新。angularjs
的這種做法,徹底將開發者從操作 DOM
上解放了出來(為jq沒落埋下伏筆),自此之後開發者只用專注 Model
層的資料加工以及業務處理,至於頁面如何渲染全權交給 angularjs
底層處理即好了。
但需要注意的是,angularjs
在當時並沒有虛擬dom
的概念,那它是怎麼做感知資料層變化以及更新檢視層的呢?angularjs
有一套髒檢測機制$digest
,html
中凡是使用了模板語法{{}}
或者ng-bind
指令的部分,都會被加入到髒檢測的warchers
列表中,它是一個陣列,之後只要使用者通過ng-click(與傳統click不同,內建繫結了觸發髒檢測的機制)
等方法改變了Model
的資料,angularjs
就會從頂層rootScope
向下遞迴,依次存取每個子scope
中的warchers
列表,並對其中監聽的部分做新舊對比,如果不同則進行資料替換,以及DOM
層的更新。
但是你要想想,一個應用那麼大的結構,只要某一個資料變化了就得從頂層向下對比N個子 scope
中 warchers
下的所有監聽物件,全量對比的效能有多差可想而知,angularjs
自身也意識到了這點,所以之後直接放棄了 angularjs
的維護轉而新開了 angular
專案。
對於 angularjs
髒檢測感興趣可以讀讀博主深入瞭解angularjs中的