寫在前面
此系列來源于開源項目:前端 100 問:能搞懂 80%的請把簡歷給我
為了備戰(zhàn) 2021 春招
每天一題越庇,督促自己
從多方面多角度總結答案奋隶,豐富知識
Virtual DOM 真的比操作原生 DOM 快嗎?談談你的想法悦荒。
簡書整合地址:前端 100 問
正文回答
作者:尤雨溪
鏈接:https://www.zhihu.com/question/31809713/answer/53544875
來源:知乎
原生 DOM 操作 vs. 通過框架封裝操作。
這是一個性能 vs. 可維護性的取舍嘹吨“嵛叮框架的意義在于為你掩蓋底層的 DOM 操作,讓你用更聲明式的方式來描述你的目的,從而讓你的代碼更容易維護碰纬。沒有任何框架可以比純手動的優(yōu)化 DOM 操作更快萍聊,因為框架的 DOM 操作層需要應對任何上層 API 可能產生的操作,它的實現必須是普適的悦析。針對任何一個 benchmark寿桨,我都可以寫出比任何框架更快的手動優(yōu)化,但是那有什么意義呢强戴?在構建一個實際應用的時候亭螟,你難道為每一個地方都去做手動優(yōu)化嗎?出于可維護性的考慮骑歹,這顯然不可能预烙。框架給你的保證是道媚,你在不需要手動優(yōu)化的情況下扁掸,我依然可以給你提供過得去的性能。
對 React 的 Virtual DOM 的誤解最域。
React 從來沒有說過 “React 比原生操作 DOM 快”谴分。React 的基本思維模式是每次有變動就整個重新渲染整個應用。如果沒有 Virtual DOM镀脂,簡單來想就是直接重置 innerHTML牺蹄。很多人都沒有意識到,在一個大型列表所有數據都變了的情況下狗热,重置 innerHTML 其實是一個還算合理的操作... 真正的問題是在 “全部重新渲染” 的思維模式下钞馁,即使只有一行數據變了,它也需要重置整個 innerHTML匿刮,這時候顯然就有大量的浪費僧凰。
Virtual DOM render + diff 顯然比渲染 html 字符串要慢,但是熟丸!它依然是純 js 層面的計算训措,比起后面的 DOM 操作來說,依然便宜了太多光羞〖可以看到,innerHTML 的總計算量不管是 js 計算還是 DOM 操作都是和整個界面的大小相關纱兑,但 Virtual DOM 的計算量里面呀闻,只有 js 計算和界面大小相關,DOM 操作是和數據的變動量相關的潜慎。前面說了捡多,和 DOM 操作比起來蓖康,js 計算是極其便宜的。這才是為什么要有 Virtual DOM:它保證了 1)不管你的數據變化多少垒手,每次重繪的性能都可以接受蒜焊;2) 你依然可以用類似 innerHTML 的思路去寫你的應用。
MVVM vs. Virtual DOM
相比起 React科贬,其他 MVVM 系框架比如 Angular, Knockout 以及 Vue泳梆、Avalon 采用的都是數據綁定:通過 Directive/Binding 對象,觀察數據變化并保留對實際 DOM 元素的引用榜掌,當有數據變化時進行對應的操作优妙。MVVM 的變化檢查是數據層面的,而 React 的檢查是 DOM 結構層面的唐责。
MVVM 的性能也根據變動檢測的實現原理有所不同:Angular 的臟檢查使得任何變動都有固定的
O(watcher count) 的代價鳞溉;Knockout/Vue/Avalon 都采用了依賴收集,在 js 和 DOM 層面都是 O(change):
- 臟檢查:scope digest O(watcher count) + 必要 DOM 更新 O(DOM change)
- 依賴收集:重新收集依賴 O(data change) + 必要 DOM 更新 O(DOM change)
可以看到鼠哥,Angular 最不效率的地方在于任何小變動都有的和 watcher 數量相關的性能代價熟菲。但是!當所有數據都變了的時候朴恳,Angular 其實并不吃虧抄罕。依賴收集在初始化和數據變化的時候都需要重新收集依賴,這個代價在小量更新的時候幾乎可以忽略于颖,但在數據量龐大的時候也會產生一定的消耗呆贿。
MVVM 渲染列表的時候,由于每一行都有自己的數據作用域森渐,所以通常都是每一行有一個對應的 ViewModel 實例做入,或者是一個稍微輕量一些的利用原型繼承的 "scope" 對象,但也有一定的代價同衣。所以竟块,MVVM 列表渲染的初始化幾乎一定比 React 慢,因為創(chuàng)建 ViewModel / scope 實例比起 Virtual DOM 來說要昂貴很多耐齐。這里所有 MVVM 實現的一個共同問題就是在列表渲染的數據源變動時浪秘,尤其是當數據是全新的對象時,如何有效地復用已經創(chuàng)建的 ViewModel 實例和 DOM 元素埠况。假如沒有任何復用方面的優(yōu)化耸携,由于數據是 “全新” 的,MVVM 實際上需要銷毀之前的所有實例辕翰,重新創(chuàng)建所有實例夺衍,最后再進行一次渲染!這就是為什么題目里鏈接的 angular/knockout 實現都相對比較慢喜命。相比之下沟沙,React 的變動檢查由于是 DOM 結構層面的的畴,即使是全新的數據,只要最后渲染結果沒變尝胆,那么就不需要做無用功。
Angular 和 Vue 都提供了列表重繪的優(yōu)化機制护桦,也就是 “提示” 框架如何有效地復用實例和 DOM 元素含衔。比如數據庫里的同一個對象,在兩次前端 API 調用里面會成為不同的對象二庵,但是它們依然有一樣的 uid贪染。這時候你就可以提示 track by uid 來讓 Angular 知道,這兩個對象其實是同一份數據催享。那么原來這份數據對應的實例和 DOM 元素都可以復用杭隙,只需要更新變動了的部分∫蛎睿或者痰憎,你也可以直接 track by index 的話铣耘,后續(xù)重繪是不會比 React 慢多少的。甚至在 dbmonster 測試中以故,Angular 和 Vue 用了 track by $index 以后都比 React 快: dbmon (注意 Angular 默認版本無優(yōu)化蜗细,優(yōu)化過的在下面)
性能比較也要看場合
在比較性能的時候,要分清楚初始渲染怒详、小量數據更新炉媒、大量數據更新這些不同的場合。Virtual DOM昆烁、臟檢查 MVVM吊骤、數據收集 MVVM 在不同場合各有不同的表現和不同的優(yōu)化需求。
Virtual DOM 為了提升小量數據更新時的性能善玫,也需要針對性的優(yōu)化水援,比如 shouldComponentUpdate 或是 immutable data。
總結
以上這些比較茅郎,更多的是對于框架開發(fā)研究者提供一些參考蜗元。主流的框架 + 合理的優(yōu)化,足以應對絕大部分應用的性能需求系冗。如果是對性能有極致需求的特殊情況奕扣,其實應該犧牲一些可維護性采取手動優(yōu)化:比如 Atom 編輯器在文件渲染的實現上放棄了 React 而采用了自己實現的 tile-based rendering;又比如在移動端需要 DOM-pooling 的虛擬滾動掌敬,不需要考慮順序變化惯豆,可以繞過框架的內置實現自己搞一個池磁。