React中一個沒人能解釋清楚的問題——爲什麼要使用Virtual DOM

React中一個沒人能解釋清楚的問題——爲什麼要使用Virtual DOM

本文轉載自:衆成翻譯
譯者:TinkGu
鏈接:http://www.zcfy.cc/article/1211
原文:https://hashnode.com/post/the-one-thing-that-no-one-properly-explains-about-react-why-virtual-dom-cisczhfj41bmssp53mvfwmgrq

有一天,我的朋友向我提了一個有關React的問題:

組件化, 單向數據綁定,這些我都懂了。但是React爲什麼要用Virtual DOM呢?

我的回答非常套路,“因爲直接操作DOM比較低效,比較慢。”。

“但是現在的js引擎總是搞個大新聞,說自己的性能比之前又要不知道高到哪裏去了。既然如此,爲什麼還會說直接操作DOM比較慢呢?”

好吧… 這確實是一個好問題。

驚人的是,我找了半天,發現並沒有任何一篇文章可以給出堅如磐石的證明,來完滿地解釋Virtual DOM的必要性。
其實,使得整個流程變得低效的,並不只有直接操作DOM,還包括了操作DOM之後發生的事情。

爲了能讓你更好地理解Virtual DOM的必要性,我們先來個急轉彎,從宏觀上來看瀏覽器的工作流。以及,一次DOM更新後,到底會發生什麼事呢?

瀏覽器工作流

NOTE:在下面這張圖中,配圖文字使用的是Webkit引擎的術語。所有的瀏覽器都是遵循類似的工作流,僅在細節處略有不同。

瀏覽器工作流

創建DOM樹

  • 一旦瀏覽器接收到一個HTML文件,渲染引擎(render engine)就開始解析它,並根據HTML元素(elements)一一對應地生成DOM 節點(nodes),組成一棵DOM樹。

創建渲染樹

  • 同時,瀏覽器也會解析來自外部CSS文件和元素上的inline樣式。通常瀏覽器會爲這些樣式信息,連同包含樣式信息的DOM樹上的節點,再創建另外一個樹,一般被稱作渲染樹(render tree

創建渲染樹背後的故事

  • WebKit內核的瀏覽器上,處理一個節點的樣式的過程稱爲attachment。DOM樹上的每個節點都有一個attach方法,它接收計算好的樣式信息,返回一個render對象(又名renderer
  • Attachment的過程是同步的,新節點插入DOM樹時,會調用新節點的attach方法。
  • 構建渲染樹時,由於包含了這些render對象,每個render對象都需要計算視覺屬性(visual properties);這個過程通過計算每個元素的樣式屬性來完成。

佈局 Layout

又被簡稱爲Reflow[2]

  • 構造了渲染樹以後,瀏覽器引擎開始着手佈局(layout)。佈局時,渲染樹上的每個節點根據其在屏幕上應該出現的精確位置,分配一組屏幕座標值。

繪製 Painting

  • 接着,瀏覽器將會通過遍歷渲染樹,調用每個節點的paint方法來繪製這些render對象。paint方法根據瀏覽器平臺,使用不同的UI後端API(agnostic UI backend API)。
    通過繪製,最終將在屏幕上展示內容。

再來看Virtual DOM

好啦,現在你已經簡單過了一遍瀏覽器引擎的渲染流程,你可以看到,從創建渲染樹,到佈局,一直到繪製,只要你在這過程中進行一次DOM更新,整個渲染流程都會重做一遍。尤其是創建渲染樹,它需要重新計算所有元素上的所有樣式。

在一個複雜的單頁面應用中,經常會涉及到大量的DOM操作,這將引起多次計算,使得整個流程變得低效,這應該儘量避免。

Virtual DOM這個抽象層真正的閃光點正在於此:每當你想對視圖進行一次更新,那些本該直接作用於真實DOM的改動,都會先作用於Virtual DOM,然後再將要改動的部分通知到真實DOM。這樣可以大幅減少DOM操作帶來的重計算步驟。

Update: Reddit上的 ugwe43to874nf4 對Virtual DOM的重要性做了更客觀的評價。

DOM 操作 真正的問題在於每次操作都會觸發佈局的改變、DOM樹的修改和渲染。所以,當你一個接一個地去修改30個節點的時候,就會引起30次(潛在的)佈局重算,30次(潛在的)重繪,等等。

Virtual DOM 實際上沒有使用什麼全新的技術,僅僅是把 “ 雙緩衝(double buffering)” 技術應用到了DOM上面。
這樣一來,當你在這個單獨的虛擬的DOM樹上也一個接一個地修改30個節點的時候,它不會每次都去觸發重繪,所以修改節點的開銷就變小了。
之後,一旦你要把這些改動傳遞給真實DOM,之前所有的改動就會整合成一次DOM操作。這一次DOM操作引起的佈局計算和重繪可能會更大,但是相比而言,整合起來的改動只做一次,減少了(多次)計算。

不過,實際上不借助Virtual DOM也可以做到這一點。你可以自己手動地整合所有的DOM操作到一個DOM 碎片(DOM fragment) 裏,然後再傳遞給DOM樹。

既然如此,我們再來看看Virtual DOM到底解決了什麼問題。
首先,它把管理DOM碎片這件事情自動化、抽象化了,使得你無需再去手動處理。另外,當你要手動去做這件事情的時候,你還得記得哪些部分變化了,哪些部分沒變,畢竟之後重繪時,DOM樹上的大量細節你都不需要重新刷新。這時候Virtual DOM的自動化對你來說就非常有用了,如果它的實現是正確的,那麼它就會知道到底哪些地方應該需要刷新,哪些地方不要。

最後,Virtual DOM通過各種組件和你寫的一些代碼來請求對它進行操作,而不是直接對它本身進行操作,使你不必非要跟Virtual DOM交互,也不必非要去了解Virtual DOM修改DOM樹的原理,也就不用再想着去修改DOM了。(譯註:對開發者來說,Virtual DOM幾乎是完全透明的)。這樣你就不用在 修改DOM整合DOM操作爲一次 之間做同步處理了。

進一步閱讀

以上關於瀏覽器工作流的內容摘錄自這篇文檔中關於瀏覽器內部行爲的章節。這篇文章還深入解釋了瀏覽器引擎的hood部分的一切細節。毋庸置疑,這篇文章值得你花時間從頭到尾好好讀一遍。它會幫你很好地理解爲什麼我們需要Virtual DOM這樣一個額外的抽象層。

譯註

  • [ 1 ] a 3000 feet level view 如果翻譯成 “一個達到30000英尺這種級別的視圖”,總覺得怪怪的,於是翻譯爲一個超大型的視圖。 如果哪位知道信達雅的翻譯,請評論留言分享一下,謝謝!

  • [ 2 ] Webkit 裏使用layout表示元素的佈局,Gecko則稱爲Reflow

  • [ 3 ] 遵循慣例,文中一律將node(特指DOM樹上的一個單元)翻譯爲節點;將element(特指HTML上的一個單元)翻譯爲元素

  • [ 4 ] Virtual DOM虛擬 DOM。考慮到這詞幾乎已經稱爲一個專門的術語了,一般大家也直接用英文稱呼,不翻譯可能更具可讀性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章