在Web開發中,須要將數據的變化實時反映到UI上,這時就須要對DOM進行操做,可是複雜或頻繁的DOM操做一般是性能瓶頸產生的緣由,爲此,React引入了虛擬DOM(Virtual DOM)的機制。html
在React中,render執行的結果獲得的並非真正的DOM節點,結果僅僅是輕量級的JavaScript對象,咱們稱之爲virtual DOM。前端
虛擬DOM是React的一大亮點,具備batching(批處理)和高效的Diff算法。這讓咱們能夠無需擔憂性能問題而」毫無顧忌」的隨時「刷新」整個頁面,由虛擬 DOM來確保只對界面上真正變化的部分進行實際的DOM操做。在實際開發中基本無需關心虛擬DOM是如何運做的,可是理解其運行機制不只有助於更好的理解React組件的生命週期,並且對於進一步優化 React程序也會有很大幫助。vue
若是沒有 Virtual DOM,簡單來講就是直接重置 innerHTML。這樣操做,在一個大型列表全部數據都變了的狀況下,還算是合理,可是,當只有一行數據發生變化時,它也須要重置整個 innerHTML,這時候顯然就形成了大量浪費。react
比較innerHTML 和Virtual DOM 的重繪過程以下:git
innerHTML: render html string + 從新建立全部 DOM 元素github
Virtual DOM: render Virtual DOM + diff + 必要的 DOM 更新算法
和 DOM 操做比起來,js 計算是很是便宜的。Virtual DOM render + diff 顯然比渲染 html 字符串要慢,可是,它依然是純 js 層面的計算,比起後面的 DOM 操做來講,依然便宜了太多。固然,曾有人作過驗證說React的性能不如直接操做真實DOM,代碼以下:數據庫
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
function Raw() {
var data = _buildData(),
html = "";
...
for(var i=0; i<data.length; i++) {
var render = template;
render = render.replace("{{className}}", "");
render = render.replace("{{label}}", data[i].label);
html += render;
}
...
container.innerHTML = html;
...
}
|
該測試用例中雖然構造了一個包含1000個Tag的String,並把它添加到DOM樹中,可是隻作了一次DOM操做。然而,在實際開發過程當中,這1000個元素更新可能分佈在20個邏輯塊中,每一個邏輯塊中包含50個元素,當頁面須要更新時,都會引發DOM樹的更新,上述代碼就近似變成了以下格式:sublime-text
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function Raw() {
var data = _buildData(),
html = "";
...
for(var i=0; i<data.length; i++) {
var render = template;
render = render.replace("{{className}}", "");
render = render.replace("{{label}}", data[i].label);
html += render;
if(!(i % 50)) {
container.innerHTML = html;
}
}
...
}
|
這樣來看,React的性能就遠勝於原生DOM操做了。數組
React.js 相對於直接操做原生DOM有很大的性能優點, 很大程度上都要歸功於virtual DOM的batching 和diff。batching把全部的DOM操做蒐集起來,一次性提交給真實的DOM。diff算法時間複雜度也從標準的的Diff算法的O(n^3)降到了O(n)。這裏留到下一次博客單獨講。
能夠看到,Angular 最不效率的地方在於任何小變更都有的和 watcher 數量相關的性能代價。可是!當全部數據都變了的時候,Angular 其實並不吃虧。依賴收集在初始化和數據變化的時候都須要從新收集依賴,這個代價在小量更新的時候幾乎能夠忽略,但在數據量龐大的時候也會產生必定的消耗。
MVVM 渲染列表的時候,因爲每一行都有本身的數據做用域,因此一般都是每一行有一個對應的 ViewModel 實例,或者是一個稍微輕量一些的利用原型繼承的 "scope" 對象,但也有必定的代價。因此,MVVM 列表渲染的初始化幾乎必定比 React 慢,由於建立 ViewModel / scope 實例比起 Virtual DOM 來講要昂貴不少。這裏全部 MVVM 實現的一個共同問題就是在列表渲染的數據源變更時,尤爲是當數據是全新的對象時,如何有效地複用已經建立的 ViewModel 實例和 DOM 元素。假如沒有任何複用方面的優化,因爲數據是 「全新」 的,MVVM 實際上須要銷燬以前的全部實例,從新建立全部實例,最後再進行一次渲染!這就是爲何題目裏連接的 angular/knockout 實現都相對比較慢。相比之下,React 的變更檢查因爲是 DOM 結構層面的,即便是全新的數據,只要最後渲染結果沒變,那麼就不須要作無用功。
Angular 和 Vue 都提供了列表重繪的優化機制,也就是 「提示」 框架如何有效地複用實例和 DOM 元素。好比數據庫裏的同一個對象,在兩次前端 API 調用裏面會成爲不一樣的對象,可是它們依然有同樣的 uid。這時候你就能夠提示 track by uid 來讓 Angular 知道,這兩個對象實際上是同一份數據。那麼原來這份數據對應的實例和 DOM 元素均可以複用,只須要更新變更了的部分。或者,你也能夠直接 track by $index 來進行 「原地複用」:直接根據在數組裏的位置進行復用。在題目給出的例子裏,若是 angular 實現加上 track by $index 的話,後續重繪是不會比 React 慢多少的。甚至在 dbmonster 測試中,Angular 和 Vue 用了 track by $index 之後都比 React 快: dbmon (注意 Angular 默認版本無優化,優化過的在下面)
在比較性能的時候,要分清楚初始渲染、小量數據更新、大量數據更新這些不一樣的場合。Virtual DOM、髒檢查 MVVM、數據收集 MVVM 在不一樣場合各有不一樣的表現和不一樣的優化需求。Virtual DOM 爲了提高小量數據更新時的性能,也須要針對性的優化,好比 shouldComponentUpdate 或是 immutable data。
(該段落借鑑了知乎的相關回答)
React 歷來沒有說過 「React 比原生操做 DOM 快」。React給咱們的保證是,在不須要手動優化的狀況下,它依然能夠給咱們提供過得去的性能。