注意: 虛擬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的問題了, 需要對自定義標簽進行更多的處理!
目前avalon的虛擬DOM方案也在緩慢推進中,不是我不會寫parser,只是太多人問我許多avalon基礎問題,嚴重拖慢我的速度。。。。