采用尤大大的回答:
1. 原生 DOM 操作 vs. 通過框架封裝操作。
這是一個(gè)性能 vs. 可維護(hù)性的取舍薪鹦。框架的意義在于為你掩蓋底層的 DOM 操作惯豆,讓你用更聲明式的方式來描述你的目的池磁,從而讓你的代碼更容易維護(hù)。沒有任何框架可以比純手動(dòng)的優(yōu)化 DOM 操作更快楷兽,因?yàn)榭蚣艿?DOM 操作層需要應(yīng)對任何上層 API 可能產(chǎn)生的操作地熄,它的實(shí)現(xiàn)必須是普適的。針對任何一個(gè) benchmark芯杀,我都可以寫出比任何框架更快的手動(dòng)優(yōu)化端考,但是那有什么意義呢?在構(gòu)建一個(gè)實(shí)際應(yīng)用的時(shí)候揭厚,你難道為每一個(gè)地方都去做手動(dòng)優(yōu)化嗎却特?出于可維護(hù)性的考慮,這顯然不可能筛圆×衙鳎框架給你的保證是,你在不需要手動(dòng)優(yōu)化的情況下太援,我依然可以給你提供過得去的性能闽晦。
2. 對 React 的 Virtual DOM 的誤解扳碍。
React 從來沒有說過 “React 比原生操作 DOM 快”。React 的基本思維模式是每次有變動(dòng)就整個(gè)重新渲染整個(gè)應(yīng)用尼荆。如果沒有 Virtual DOM左腔,簡單來想就是直接重置 innerHTML。很多人都沒有意識到捅儒,在一個(gè)大型列表所有數(shù)據(jù)都變了的情況下液样,重置 innerHTML 其實(shí)是一個(gè)還算合理的操作... 真正的問題是在 “全部重新渲染” 的思維模式下,即使只有一行數(shù)據(jù)變了巧还,它也需要重置整個(gè) innerHTML鞭莽,這時(shí)候顯然就有大量的浪費(fèi)。
我們可以比較一下 innerHTML vs. Virtual DOM 的重繪性能消耗:
- innerHTML: render html string O(template size) + 重新創(chuàng)建所有 DOM 元素 O(DOM size)
- Virtual DOM: render Virtual DOM + diff O(template size) + 必要的 DOM 更新 O(DOM change)
Virtual DOM render + diff 顯然比渲染 html 字符串要慢麸祷,但是澎怒!它依然是純 js 層面的計(jì)算,比起后面的 DOM 操作來說阶牍,依然便宜了太多喷面。可以看到走孽,innerHTML 的總計(jì)算量不管是 js 計(jì)算還是 DOM 操作都是和整個(gè)界面的大小相關(guān)惧辈,但 Virtual DOM 的計(jì)算量里面,只有 js 計(jì)算和界面大小相關(guān)磕瓷,DOM 操作是和數(shù)據(jù)的變動(dòng)量相關(guān)的盒齿。前面說了,和 DOM 操作比起來困食,js 計(jì)算是極其便宜的边翁。這才是為什么要有 Virtual DOM:它保證了 1)不管你的數(shù)據(jù)變化多少,每次重繪的性能都可以接受硕盹;2) 你依然可以用類似 innerHTML 的思路去寫你的應(yīng)用符匾。
3. MVVM vs. Virtual DOM
相比起 React,其他 MVVM 系框架比如 Angular, Knockout 以及 Vue莱睁、Avalon 采用的都是數(shù)據(jù)綁定:通過 Directive/Binding 對象待讳,觀察數(shù)據(jù)變化并保留對實(shí)際 DOM 元素的引用,當(dāng)有數(shù)據(jù)變化時(shí)進(jìn)行對應(yīng)的操作仰剿。MVVM 的變化檢查是數(shù)據(jù)層面的创淡,而 React 的檢查是 DOM 結(jié)構(gòu)層面的。MVVM 的性能也根據(jù)變動(dòng)檢測的實(shí)現(xiàn)原理有所不同:Angular 的臟檢查使得任何變動(dòng)都有固定的
O(watcher count) 的代價(jià)南吮;Knockout/Vue/Avalon 都采用了依賴收集琳彩,在 js 和 DOM 層面都是 O(change):
- 臟檢查:scope digest O(watcher count) + 必要 DOM 更新 O(DOM change)
- 依賴收集:重新收集依賴 O(data change) + 必要 DOM 更新 O(DOM change)可以看到,Angular 最不效率的地方在于任何小變動(dòng)都有的和 watcher 數(shù)量相關(guān)的性能代價(jià)。但是露乏!當(dāng)所有數(shù)據(jù)都變了的時(shí)候碧浊,Angular 其實(shí)并不吃虧。依賴收集在初始化和數(shù)據(jù)變化的時(shí)候都需要重新收集依賴瘟仿,這個(gè)代價(jià)在小量更新的時(shí)候幾乎可以忽略箱锐,但在數(shù)據(jù)量龐大的時(shí)候也會(huì)產(chǎn)生一定的消耗。
MVVM 渲染列表的時(shí)候劳较,由于每一行都有自己的數(shù)據(jù)作用域驹止,所以通常都是每一行有一個(gè)對應(yīng)的 ViewModel 實(shí)例,或者是一個(gè)稍微輕量一些的利用原型繼承的 "scope" 對象观蜗,但也有一定的代價(jià)臊恋。所以,MVVM 列表渲染的初始化幾乎一定比 React 慢墓捻,因?yàn)閯?chuàng)建 ViewModel / scope 實(shí)例比起 Virtual DOM 來說要昂貴很多抖仅。這里所有 MVVM 實(shí)現(xiàn)的一個(gè)共同問題就是在列表渲染的數(shù)據(jù)源變動(dòng)時(shí),尤其是當(dāng)數(shù)據(jù)是全新的對象時(shí)砖第,如何有效地復(fù)用已經(jīng)創(chuàng)建的 ViewModel 實(shí)例和 DOM 元素撤卢。假如沒有任何復(fù)用方面的優(yōu)化,由于數(shù)據(jù)是 “全新” 的梧兼,MVVM 實(shí)際上需要銷毀之前的所有實(shí)例凸丸,重新創(chuàng)建所有實(shí)例,最后再進(jìn)行一次渲染袱院!這就是為什么題目里鏈接的 angular/knockout 實(shí)現(xiàn)都相對比較慢。相比之下瞭稼,React 的變動(dòng)檢查由于是 DOM 結(jié)構(gòu)層面的忽洛,即使是全新的數(shù)據(jù),只要最后渲染結(jié)果沒變环肘,那么就不需要做無用功欲虚。
Angular 和 Vue 都提供了列表重繪的優(yōu)化機(jī)制,也就是 “提示” 框架如何有效地復(fù)用實(shí)例和 DOM 元素悔雹。比如數(shù)據(jù)庫里的同一個(gè)對象复哆,在兩次前端 API 調(diào)用里面會(huì)成為不同的對象,但是它們依然有一樣的 uid腌零。這時(shí)候你就可以提示 track by uid 來讓 Angular 知道梯找,這兩個(gè)對象其實(shí)是同一份數(shù)據(jù)。那么原來這份數(shù)據(jù)對應(yīng)的實(shí)例和 DOM 元素都可以復(fù)用益涧,只需要更新變動(dòng)了的部分锈锤。或者,你也可以直接 track by index 的話,后續(xù)重繪是不會(huì)比 React 慢多少的阎姥。甚至在 dbmonster 測試中记舆,Angular 和 Vue 用了 track by $index 以后都比 React 快: dbmon (注意 Angular 默認(rèn)版本無優(yōu)化,優(yōu)化過的在下面)
順道說一句呼巴,React 渲染列表的時(shí)候也需要提供 key 這個(gè)特殊 prop泽腮,本質(zhì)上和 track-by 是一回事。
4. 性能比較也要看場合
在比較性能的時(shí)候伊磺,要分清楚初始渲染盛正、小量數(shù)據(jù)更新、大量數(shù)據(jù)更新這些不同的場合屑埋。Virtual DOM豪筝、臟檢查 MVVM、數(shù)據(jù)收集 MVVM 在不同場合各有不同的表現(xiàn)和不同的優(yōu)化需求摘能。Virtual DOM 為了提升小量數(shù)據(jù)更新時(shí)的性能续崖,也需要針對性的優(yōu)化,比如 shouldComponentUpdate 或是 immutable data团搞。
- 初始渲染:Virtual DOM > 臟檢查 >= 依賴收集
- 小量數(shù)據(jù)更新:依賴收集 >> Virtual DOM + 優(yōu)化 > 臟檢查(無法優(yōu)化) > Virtual DOM 無優(yōu)化
- 大量數(shù)據(jù)更新:臟檢查 + 優(yōu)化 >= 依賴收集 + 優(yōu)化 > Virtual DOM(無法/無需優(yōu)化)>> MVVM 無優(yōu)化
不要天真地以為 Virtual DOM 就是快严望,diff 不是免費(fèi)的,batching 么 MVVM 也能做逻恐,而且最終 patch 的時(shí)候還不是要用原生 API像吻。在我看來 Virtual DOM 真正的價(jià)值從來都不是性能,而是它 1) 為函數(shù)式的 UI 編程方式打開了大門复隆;2) 可以渲染到 DOM 以外的 backend拨匆,比如 ReactNative。
5. 總結(jié)
以上這些比較挽拂,更多的是對于框架開發(fā)研究者提供一些參考惭每。主流的框架 + 合理的優(yōu)化,足以應(yīng)對絕大部分應(yīng)用的性能需求亏栈。如果是對性能有極致需求的特殊情況台腥,其實(shí)應(yīng)該犧牲一些可維護(hù)性采取手動(dòng)優(yōu)化:比如 Atom 編輯器在文件渲染的實(shí)現(xiàn)上放棄了 React 而采用了自己實(shí)現(xiàn)的 tile-based rendering;又比如在移動(dòng)端需要 DOM-pooling 的虛擬滾動(dòng)绒北,不需要考慮順序變化黎侈,可以繞過框架的內(nèi)置實(shí)現(xiàn)自己搞一個(gè)。
附上尤大的回答鏈接
鏈接:https://www.zhihu.com/question/31809713/answer/53544875