render執行的結果獲得的並非真正的DOM節點,結果僅僅是輕量級的JavaScript對象,咱們稱之爲virtual DOM。html
虛擬DOM是React的一大亮點,具備batching(批處理)和高效的Diff算法。這讓咱們能夠無需擔憂性能問題而」毫無顧忌」的隨時「刷新」整個頁面,由虛擬 DOM來確保只對界面上真正變化的部分進行實際的DOM操做。前端
若是沒有 Virtual DOM,簡單來講就是直接重置 innerHTML。這樣操做,在一個大型列表全部數據都變了的狀況下,還算是合理,可是,當只有一行數據發生變化時,它也須要重置整個 innerHTML,這時候顯然就形成了大量浪費。vue
比較innerHTML 和Virtual DOM 的重繪過程以下:react
innerHTML: render html string + 從新建立全部 DOM 元素git
Virtual DOM: render Virtual DOM + diff + 必要的 DOM 更新github
和 DOM 操做比起來,js 計算是很是便宜的。Virtual DOM render + diff 顯然比渲染 html 字符串要慢,可是,它依然是純 js 層面的計算,比起後面的 DOM 操做來講,依然便宜了太多。固然,曾有人作過驗證說React的性能不如直接操做真實DOM,代碼以下:算法
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樹的更新,上述代碼就近似變成了以下格式:數據庫
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操做了。數組
並且,DOM 徹底不屬於Javascript (也不在Javascript 引擎中存在).。Javascript 實際上是一個很是獨立的引擎,DOM實際上是瀏覽器引出的一組讓Javascript操做HTML文檔的API而已。在即時編譯的時代,調用DOM的開銷是很大的。而Virtual DOM的執行徹底都在Javascript 引擎中,徹底不會有這個開銷。瀏覽器
React.js 相對於直接操做原生DOM有很大的性能優點, 很大程度上都要歸功於virtual DOM的batching 和diff。batching把全部的DOM操做蒐集起來,一次性提交給真實的DOM。diff算法時間複雜度也從標準的的Diff算法的O(n^3)降到了O(n)。這裏留到下一次博客單獨講。
相比起 React,其餘 MVVM 系框架好比 Angular, Knockout 以及 Vue、Avalon 採用的都是數據綁定:經過 Directive/Binding 對象,觀察數據變化並保留對實際 DOM 元素的引用,當有數據變化時進行對應的操做。MVVM 的變化檢查是數據層面的,而 React 的檢查是 DOM 結構層面的。MVVM 的性能也根據變更檢測的實現原理有所不一樣:Angular 的髒檢查使得任何變更都有固定的 O(watcher count) 的代價;Knockout/Vue/Avalon 都採用了依賴收集,在 js 和 DOM 層面都是 O(change):
能夠看到,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給咱們的保證是,在不須要手動優化的狀況下,它依然能夠給咱們提供過得去的性能。
React掩蓋了底層的 DOM 操做,能夠用更聲明式的方式來描述咱們目的,從而讓代碼更容易維護。下面仍是借鑑了知乎上的回答:沒有任何框架能夠比純手動的優化 DOM 操做更快,由於框架的 DOM 操做層須要應對任何上層 API 可能產生的操做,它的實現必須是普適的。針對任何一個 benchmark,我均可以寫出比任何框架更快的手動優化,可是那有什麼意義呢?在構建一個實際應用的時候,你難道爲每個地方都去作手動優化嗎?出於可維護性的考慮,這顯然不可能。
注意:虛擬DOM只是實現MVVM的一種方案,或者說是視圖更新的一種策略。沒有虛擬DOM比MVVM更好一說。
咱們回顧傳統MVC框架,如backbone,它是將某個模板編譯成模板函數,須要更新時,是本身手動將數據總體傳入模板函數,獲得一個字符串,使用innerHTML刷新某個容器!注意,這裏其實能夠優化,但因爲是手動,是體力活,都是使用很粗放型的innerhTML了事(使用jQuery的html方法性能會更差,不過好處是它處理了IE下的innerHTML BUG及全平臺的沒法執行內部的script標籤的BUG) 因爲總體替換,一會兒銷燬這麼多元素(有時還綁着事件,可能致使GC出問題),又要插入這麼多元素,再從新綁定事件(這個可使用事件代理緩解) 所以性能很是差
方案二是使用髒檢測的angular,要求對全部做用域對象進行diff,使用通知刷新函數進行視圖更新. 頁面上的指令越多,須要比較的數據越多(有循環, 須要乘以數組長度或對象鍵值對個數),可能用於循環時間過長致使頁面假死
方案三使用avalon這樣的密封艙方案(船底是分紅一個個獨立的區域,局部受損也不會致使沉船).avalon使用Object.defineProperty及VBS實現屬性監控, 這樣用戶修數據時,就能當即進入。事件總線系統(觀察者模式), 而後取得與這屬性相關聯的訂閱者數組(換言之的密封艙,不像ng那樣,一個$scope對象就一個$$watcher數組).
而通常狀況下,VM中的某個屬性在視圖中也只會用到幾個位置,那麼幾個位置,就會生成幾個綁定對象,都放在相應的訂閱者數組中,每一個訂閱者數組都不會太長.所以同步視圖,不會所以遍歷的數組過長而假死.所以ng在處理2000個指令的頁面時就易出問題 (一個grid,每每有兩三重循環,很容就飆到5000個指令),而avalon的密封艙方案是能撐到12000個指令。但avalon須要保存大量的綁定對象,而且將普通屬性轉換訪問器屬性,也須要佔用內存,這是一個以空間換時間的方案.
不過avalon在處理ms-repeat, ms-with, ms-each這個循環綁定時,也實現得不太好,這其中要生成大量的代理VM,整個頁面都在生成銷燬VM中拖慢了(即使使用各類池子進行循環再用).
方案四,像knockout那樣, 使用時讓用戶痛苦一些,使用可同步視圖的東西用函數(wrapper)包裹起來,刷新視圖,就只須要從新調用這個wrapper.如今全部新的MVVM都是從ko那裏學到依賴收集.這個wraper會通知其依賴的wrapper,經過極其痛苦晦澀的方式進入事件總線, 執行視圖刷新函數. knockout是使用閉包用到極致的庫,顯然這樣作性能也不好.
最後react, 首先使用編譯手段(jsx的虛擬DOM轉換), 將這部分消耗能提早釋放出去,不過將字符串(jsx模板)轉換爲一個個JS對象,也佔不了多少內存. 而後是數據發生變更時,因爲數據變更都是須要用setState方法,所以兼容性很好,少了Object.defineProperty或wrapper的消耗,而後對應數據經過render轉換成字符串,字符串再轉換虛擬DOM樹。先後虛擬DOM進行比較, 更新視圖.
react是面向組件設計, 一個組件就是一個密封艙, 不多會對全部虛擬DOM進行比較, 因爲強制使用單向流動, 減小每次變更須要的diff. 沒有綁定對象與wrapper的內存佔用高的問題.
react的流行,只是ng太難用了,當ng或其餘MVVM改用虛擬DOM進行視圖更新,這優點就不須要!
react的問題很明顯,庫很是大,它基本上離開了jsx換轉器就活不成。 這麼大的庫, 換言之你們能對它改動的地方就越多,每升一個版本就數千改動。做爲架構師的咱們,須要對其源碼進行很是熟悉的瞭解,要不出了問題沒法本身處理,每次等外國人回覆就遲了。
react的複雜度,很易觸發你們對它的重構,即使佔有方有向前兼容的願望,但能抵得幾回誘惑呢,所以通過幾個版本會面目全非。若是你堅持不變,那麼其餘人就會另起山頭, 開源的東西很易出現一個更優點的仿品!react的實現很糟糕的,強在設計!
虛擬DOM的難點是如何將一個字符串變成一個模板函數,而後再轉換爲虛擬DOM。 目前沒有簡單的HTML parser實現,stackoverflow上說不能使用幾行正則就能拆分HTML! 所以這個高門檻,致使react的代替方案難產!github上有許多自稱使用了虛擬DOM的框架,不是假的就是超垃圾的實現!更況且react支持自定義標籤,所以不單是解析HTML的問題了,須要對自定義標籤進行更多的處理!
--摘自2篇文章