既然用 virtual dom 可以提高性能,為什麼瀏覽器不直接自帶這個功能呢?


emmmm....

看代碼。(瀏覽器是火狐nightly,chrome的表現不一定一樣)

function wait5s() {
const pre = Date.now()
while(Date.now()-pre&<5000){} } console.log("在這兒停頓!") wait5s(); console.log("累死了,嘿!")

會發生什麼?瀏覽器會卡住-w-

嗯,要的效果就是卡住……好孩子不要學喲?

然後,我們有個div,大概是這樣的:

&&

因為是粉紅色的(大概吧)方塊,所以我們叫它粉紅君吧。

然後,我們改一改js……

function wait5s() {
const pre = Date.now()
while(Date.now()-pre&<5000){} } console.log("在這兒停頓!") document.getElementById("box").style.width = "200px" // 看這裡喲看這裡 wait5s(); console.log("累死了,嘿!")

發生了什麼事情~?

真的停頓了,粉紅君在五秒鐘之後才從100px長胖成了200px……

這是為什麼喲?emmmmm....因為瀏覽器卡住了呀。(逃

我們繼續改代碼呀改代碼。

function wait5s() {
const pre = Date.now()
while (Date.now() - pre &< 1500) { } } const box = document.getElementById("box"); function setWidth(w) { box.style.width = w + "px" } const test = () =&> {
console.log("在這兒停頓!")
setWidth(200)
wait5s();
setWidth(300)
wait5s();
console.log("累死了,嘿!")
}
// setTimeout(test,0)
setTimeout(test,10)
// setTimeout(test,20)
// Promise.resolve().then(test)

結果是,在Firefox Nightly下面(2017/11/02),0毫秒,10毫秒的setTimeout以及Promise.resolve()都不會繪製出width為100px的盒子,直接在……嗯,其實是三秒後……繪製300px的盒子。

而Chrome下面只有Promise不會繪製width為100px的盒子,只要是setTimeout一定會繪製出100px的盒子。

哇哦。

看來瀏覽器也不是數據改了之後就立刻渲染的吧?

對於React來說,它修改了DOM對象就算是Render了對吧?對於瀏覽器來說,它需要修改屏幕上的畫面就算是Render了對吧?

於是,結論是:

既然React那玩意兒叫Virtual DOM,辣么瀏覽器的是不是DOM也可以叫Virtual Render呀~?(逃

抱歉,那是開玩笑的。

我是說,瀏覽器和js框架的層級是不一樣的,所以會提供不同的API,各司其職。

render前先batch之類的,瀏覽器優化是肯定優化了的,不可能不做優化的,做瀏覽器的人個個都是人才,代碼寫的又好看……(咦

換個說法,假如要集成一個virtual dom到瀏覽器裡面,你想要集成怎樣的virtual dom給瀏覽器呢?

設置了css屬性之後不觸發樣式重計算/布局的嗎?

然而樣式重計算/布局這個的消耗真的不算大呀。隨便亂改反而可能導致一大堆數據不一致的bug……

PS:

其實有些同學沒有看明白「virtual DOM比原生快」這個梗是哪裡來的。我覺得我可以簡單講一下……(當然 也可能不對)

react剛出的時候,AngularJs還是如日中天。ng1.x裡面操作DOM是封裝在指令裡面的,用的也的確是原生DOM操作。但是ng1.x那時候還有許多注入digest循環最多可能跑十遍才穩定之類的奇奇怪怪設定。然後有些人(包括我自己)寫的指令,在被觸發時,裡面的所有操作也會重新做一遍。

於是,react比ng1.x快當然是必然的,然後就微妙的變成了virtual DOM比原生快這個奇妙的謠言……

其實類似的梗還有post比get更安全之類的……(逃


單純VDOM是提高不了性能的,VDOM主要作用在於它的二次抽象提供了一個diff/patch和batch commit的機會。

這個和使用圖形的時候減少Draw Call的優化方針基本上是一個指導思想,在應用層面做合理的組織,降低那些代價大的開銷的頻率。Vulkan之於OpenGL就把一些這類優化下移到了系統和驅動的層面上,但是也帶來了不少的Breaking Change。

如果這真的是勢在必行的,也許會有新的DOM API被設計出來,跟現在不完全兼容,跟現在的開發方式也會產生不少區別。而至今還沒出現,那我覺得就是需求沒那麼強烈吧…


既然CRT每個C程序都用到,為什麼不做成內核的API?

KISS原則

如果機制層做了太多策略層的工作,不僅工作量爆炸,而且背負了太多向前兼容的顧慮。(其實瀏覽器的功能已經接近爆炸了)

大家不要忘了,就在前幾年,有一種常見意見是「既然所有網站都要用到jQuery,乾脆讓瀏覽器自帶不就好了?」


Virtual DOM只有在重複渲染的時候才可能提高性能,畢竟要多一個運算步驟,也要消耗更多的內存,只渲染一次,不會獲得任何性能的好處。

瀏覽器傳統的渲染方式有兩種:

  1. 給一個HTML字元串,解析,變成DOM Tree,畫出來;
  2. 對一個或者一組選中的DOM進行操作。

這兩種操作方式都很原始很底層,正是瀏覽器該做的事情,至於怎麼做單頁應用,怎麼在重複渲染中提高性能,是應用層做的事情。

所以說,瀏覽器發展總是要慢網頁應用一拍啊,好多事情都是應用層先實現了,然後才推動瀏覽器層去實現。

至於瀏覽器能不能實現Virtual DOM,當然能,實際上,已經有這個Proposal了 trueadm/t7 。


【大多數答案都沒答到要點……】

「直接操作 DOM 性能差」是因為 ——

  1. DOM 引擎JS 引擎 相互獨立,但又工作在同一線程(主線程)
  2. JS 代碼調用 DOM API 必須 掛起 JS 引擎、轉換傳入參數數據、激活 DOM 引擎,DOM 重繪後再轉換可能有的返回值,最後激活 JS 引擎並繼續執行
  3. 若有頻繁的 DOM API 調用,且瀏覽器廠商不做「批量處理」優化,引擎間切換的單位代價將迅速積累
  4. 若其中有強制重繪的 DOM API 調用,不但廠商費盡心機做的「批量處理」優化被中斷,重新計算布局重新繪製圖像會引起更大的性能消耗

所以,降低引擎切換頻率減小 DOM 變更規模才是評判各種 DOM 性能優化方案的關鍵!


React 首創的 Virtual DOM 是 JS 對象樹,也有 DOM diff 演算法,符合上述關鍵條件,優化了 DOM 渲染時間;但正因為要一定程度上模擬 DOM 樹,內存佔用自然更高。

但 Facebook 在不止一種優化方案中採用 vDOM,其目標更多在於讓它作為一個兼容層,開發 React Native 這樣的跨平台 UI 庫,方便各種非 Web 系統對接。

而瀏覽器自身為何要再實現一套多此一舉的「虛擬 DOM」呢?兩套 DOM 徒增複雜度,為何不優化真實 DOM 呢?(高票答主 @Teeea 已為大家演示了現代瀏覽器在水下「潤物細無聲」的努力了,不再贅述)


而我在實現自己的聲明式 MVVM 引擎 EasyWebApp v4 時,雖然底層直接操作 DOM,但依次採用了多層優化手段 ——

  1. 對用於雙向數據綁定、但頻繁觸發的 input 事件,啟用函數節流
  2. 對每次提交給 ViewModel 的數據做 diff,只用數據差集來做重繪
  3. 對 HTML 模板中的 JS 表達式求值,在 JS 引擎中做緩存,只對變更值做 DOM API 調用

如此便實現了渲染時間、內存佔用的雙優化,歡迎各位同行來切磋 ——

  • 核心引擎 https://tech_query.oschina.io/easywebapp/docs/API/
  • 標準組件庫 BootEWA
  • 入門教程《EasyWebApp v4 應用開發與內核分析》


可是 vdom 不能提高性能,它提高的是,相對於粗粒度置換 innerhtml, diff/patch 的效率。可是為什麼瀏覽器要提供 diff/patch


想要的可以在 Proposal: DOMChangeList · Issue #270 · whatwg/dom 給 proposal 打 call


我覺得有可能,不過可能性不太高。

virtual dom渲染需要把握住入口進行batch update。在原生的web操作中並沒有卡死。因而如果支持了diff patch,也可能因為getComputedStyle、querySelector、setTimeout等操作的濫用導致頻繁reflow。這會使得virtual dom形同虛設。如果是通過createDocumentFragment來做dom update,其實性能並不差。如果想的更周到些可以使用requestAnimationFrame或者requestIdleCallback(不是所有遊覽器都支持)來進一步優化。

只說virtual dom比原生dom操作快是誤區。


我覺得問題在於React之類的框架使用數據驅動UI的開發方式,所以才產生了需要Virtual DOM對比變化來減少DOM更新量。

與之對比的應該是Backbone此類的框架,更新視圖就是直接使用模板和數據重新渲染一個完整DOM,然後替換頁面中對應的DOM。Virtual DOM是對這種渲染方法進一步優化的實現——不替換完整的DOM,只替換不同部分的DOM。

無論是整體渲染,還是通過Virtual DOM優化,都是「模板+數據 =&> 更新DOM」這個模式,而在瀏覽器層面,提供的是DOM API直接操作DOM,根本不存在「模板+數據 =&> 更新DOM」的問題。

如果要說有的話,我覺得瀏覽器對於innerHTML可能會有類似的優化。。。


正好最近在這個方面略有一些想法,所以就以回答這個問題來做一個開始。

先回答問題:

「既然用 virtual dom 可以提高性能」,這個並不盡然。如果想要說得更準確一些,應該這樣說,

因為在對複雜OnSite DOM進行操作時,會有各種事件和渲染的情況,會極大的影響對DOM的操作,所以,一般認為對於這種情況,Virtual DOM會更高效和適合一些。

這個基本上是Web老兵的常識與技術本身無關。即使在jQuery年代,也很常用,但是會有兩個明顯的斷代:

  1. innerHTML是Devil的年代:那個時代,所有的innerHTML操作都是慢到死,基本上Best Practice就是使用DOM Element進行操作,同時限於OnSite DOM本身操作性能也不高,建立一個Orphan Element,做完各種操作之後(比如Append Child),然後Swap到DOM Tree上去,這是一種常見的高級手段
  2. innerHTML已經不再低效,甚至比DOM操作還要快的時代:模板化語言開始更多的使用起來,使模板生成DOM再Swap到DOM Tree上,既簡化了開發難度,又提高了動態性, 實際上比傳統的雙向綁定方式安全且高效

再說,「為什麼瀏覽器不直接自帶這個功能呢?」,問這種問題的人,簡直對瀏覽器的歷史太不了解了。

可以說,瀏覽器是計算機中歷史最豐富,並且充滿了波瀾壯闊,絕命廝殺的歷史了。

可以參考這兩個鏈接:

History of the web browser Browser wars - Wikipedia

瀏覽器向來都是黑魔法誕生的地方,並且,瀏覽器的代碼裡面擁有太多了人類智慧的積累和各種妥協了。為什麼現在除了這幾個還活著的瀏覽器渲染引擎,連Google這種公司都不敢自己寫一個渲染引擎了呢?因為太多的兼容性了啊。。。。先不說太多,單是W3C一個組織。

All Standards and Drafts ,請數一下它已經有多少規範了,先不說實現,數一下一個人能否在一生之中有效工作時間裡面,把這些規範的每一個細節理解清楚。

瀏覽器有著比其它程序所想像不到的歷史包袱。有太多奇怪的功能,存在在那裡只是為了某一種歷史黑魔法能夠正常運行了。

所以,回過頭來說,它能不能自帶Virtual DOM的功能呢?

  1. 它本來就有,我剛剛就有說,Orphan Element就是一種Virtual DOM的概念,它擁有一切 DOM的事件,但是又不會需要動用任何瀏覽器的渲染資源
  2. 類似React這種技術的Virtual DOM(類Template,但是實際上比Template更強類型,更多語義)呢?這就是我想寫這個回答的主要原因了。

關於這個,我有三點想法:

  1. React的Virtual DOM方式並不是最好的方式

關於這個,其實可以單獨寫一篇文章,這裡就不要過多的解釋,單說結論:

* React的Virtual DOM,其根基還是在於JS託管DOM,即JS在先,DOM在後。這種方式是反瀏覽器的,對於瀏覽器來說,仍然是DOM在先,JS只是輔助,在瀏覽器的語義中,React的方式永遠不是最優解

* React的Virtual DOM,增加了系統的複雜度,並且,提高了技術的調試和處理難度。這個不言而喻,你接觸到的永遠不是React,而是HTML和JS,React是其中的一個抽象層,如果沒有像樣的工具支持,你永遠沒法Debug到你的React代碼,這是一件極其痛苦的事情(因為React會不斷發展,工具是否可以一直跟得上,只能看Facebook的工程師是不是足夠努力了)

* React的方式,增加了伺服器和客戶端的複雜度和壓力,但是所得極其有限。

先說為什麼會增加伺服器的壓力,先不說伺服器能否動態的給你編譯JS,做到Load On Need。

單說,用最普通的方式,把一堆東西Jam到一個Pack裡面(不管你做沒做DLL方式的分解),下載這個Pack就會需要點兒帶寬壓力(我說的是DEV),同時如果沒有做Tree Shaking,有80%的東西可能是你根本就沒有用到的(而且,多數時候不應該緩存)。簡直是過度浪費,做為一個從撥號年代走過來的老兵,應該覺得恥辱。

其實,客戶端就更不用說了,React的方式,與ExtJS從載入上並無區別,都是HTML Load並無鳥用,因為它們都是JS先於DOM的,DOM onLoad只是JS的開始。你需要看一個Loading界面,看一會兒,才可以真正的進入它的世界。在進入之前,你只能等,這個Boot過程其實是瀏覽器在好多年前就著手解決了的。說句很不客氣的話,除了瀏覽器,還有哪一個客戶端,可以做到在剛剛得到傳遞的內容的頭的時候,就已經開始盡最大可能的把界面渲染出來了,同時還會完美的開多個線程幫你下載其它的依賴,有效的處理所有依賴的問題,還完全沒有線程死鎖的情況?

2. 那麼,應該是什麼樣的呢?

這個部分也很多,就不在這裡詳寫了,簡單列幾點:

* 一定需要瀏覽器原生支持,Translation越少越好,這個是廢話。。。。

* 一定是DOM優先於JS,充分利用到瀏覽器本身的特性

* 一定是HTML的Super Set,可以完美兼容當前的HTML,並且可以支持模板式的語法,並且擁有自定義組件的能力

* 需要有Load On Need的支持,組件本身也應該是HTML + CSS + JS的Bundle。關於這個Bundle,我有太多要說的了。這裡先不說,留待以後寫詳細的內容

3. 那麼這種技術,實現起來應該是遙遙無期了。畢竟現在的瀏覽器實現ES6就已經很努力了(Chrome實現得非常不錯,我現在提倡主體Web開發已經可以使用Chrome直接使用ES6,不必使用Babel再脫褲子放屁一層了,至於怎麼幹掉WebPack,是另一個話題,這個部分我在構思一個項目。——一句話,對於現在的開發,Babel應該用於Live兼容,而不是開發了,你已經可以在Chrome裡面使用99%的ES6語法,為什麼還要再自己閹割一層降速降效,然後用什麼鳥SourceMap支持呢?)

並不是。從瀏覽器的歷史來看,從來都是先做瀏覽器實現,後出規範的才能成立的,先出規範的有沒有?有啊,我來講一個笑話,ES4。

那麼怎麼做一個實現呢?方法太簡單,我問問你,WebStorage是怎麼成為規範的?WebRTC是怎麼成為規範的?瀏覽器插件啊,現在的瀏覽器都已然插件化了。。。。。

那麼,這種技術大概需要什麼樣的實現呢?這個就超出本答案的範疇了。。。。。。


如果比作造房子,操作原生dom是操作真實建築材料,操作vdom是項目工程的運營,比如操作圖紙,出藍圖。

要蓋樓,不用圖紙蓋,發現錯誤要拆了重新調整,對比原生dom就是重複的操作和計算。而圖紙上,你可以虛擬計算下房子結構尺寸,受力分布,等算清楚了,圖紙定下了方案再蓋樓。如果沒圖紙,直接蓋樓,那麼你得操作真實的房梁,測試受力是否正確平衡。

但是如果你就蓋個隔板小屋,或者用木板拼接一個寵物狗房子,那麼大可不必興師動眾畫圖紙,審核圖紙,搞各種力學計算,然後出藍圖造它。

chrome,ff等瀏覽器廠商只是提供了建築材料,react,vue提供了一套工程方案,出藍圖用autocad還是手繪,項目怎麼組織執行等,這些最終會落實到操作鋼筋水泥磚塊上。

如果遇到一個牛人,造摩天樓也可以不用圖紙,不用立項,一切安排皆在腦中,那麼也可不用vdom,直接開始搬磚就行,省去了項目運營,會更快


vdom 又不是唯一的解決方案,google 不是還有個increment dom 方案么。

而且能加中間層解決的,沒必要固化到瀏覽器里。

就好像 java 也不會集成 spring 一樣。


其實本質上還是大家在操作 dom 的時候,並沒有按照瀏覽器的最優解來做吧。

如果我理解沒錯的話,vs code就是手動操作 dom 。atom 是 vdom 。但是vs code性能更好。

或者是說框架層面不造一層 vdom ,沒辦法獲知只有哪一個節點的變動,從而只更新這個節點


我在一個前端群討論過這個問題,我希望是加入一個API在vdom commit之前不要觸發layout,然後由開發者手動觸發。但問題是很多API,比如offsetTop本來就需要layout去得到最新的數據,這就是蛋雞的問題了。所以如果要搞一個這樣的東西就需要另外搞一個東西禁止在commit周期里訪問這些API。

歡迎大家在評論裡面繼續討論。


自從我手擼出「Luy,一個類React框架」(目前支持包括redux,react-redux,react router,createprotal)以後,我對Virtual DOM什麼還算是有了一點點淺薄的認識,那就來講一講我的間接好了.

一)原生DOM操作 vs 框架封裝(用Virtual DOM去操作原生DOM)

與我們手動操作dom(append)相比,用框架屏蔽dom操作,其實就是為了讓我們寫的代碼更好看點,更舒服點,當代碼越來越多,我們再也不用gay里gay氣的,小心翼翼的去操作我們的dom,而擔心dom重新的刷新。

這就是:性能和維護性的取捨

有人會說,那js在那裡跑啊跑啊的,豈不是更消耗資源?就拿react來說,需要通過diff/patch去修改更新DOM,這對於jq使用者來說,簡直就是開玩笑,老子一行代碼搞定的事情,React內部做一堆事情。實在是蛋疼。

不過代碼量上來以後,框架的好處就會很快顯示出來:無需小心翼翼,只要框架沒bug,js算就給他算一下了,無傷大雅,總比操作失誤DOM刷新/項目代碼太亂,太難維護來得強太多了。反正么,大家電腦現在誰不是8G內存,泰坦xxoo,i5i7?

所以,為了讓代碼更好維護,程序員少掉頭髮,更有時間泡妞約炮,偉大的先賢發明了各種框架來代替我們手動操作DOM。

從此,我們頭髮掉得少了,妞也有時間泡了,媽媽再也不用擔心我加班了(假的

二)Virtual DOM的誤解(React或者vue又或者所有的VDOM框架速度都很"慢「)

任何一款vdom框架,從來沒有說自己:性能快過DOM操作。還是拿react為例子,react遵循的是「每次數據變化都以最小的代價來更新真實DOM」。要做到這一點,最好的做法就是引入VDOM,在內存中比較我們的虛擬節點,然後找出哪裡的不同,然後執行更新。

但是,無論你代價再少也好,VDOM都是有消耗的,光從你要生成整個頁面的Vdom就會比單單生成原生dom要多消耗一大截內存,更別說其中的複雜遞歸,比對,然後再變換。

與真實DOM操作相比速度,vdom產品幾乎沒有什麼勝算

所以,vdom性能不是很好,尤其是react的……

三)虛擬DOM的真正價值

虛擬DOM的價值除了代替我們手動操作DOM以外,還帶來了跨平台渲染的能力。

以react native為例子。只要有內存,我們就可以在內存里塞進去一個vdom樹,然後根據vdom畫出相應平台的ui層,只不過是畫的姿勢不同而已。

vdom的抽象就像是阿拉伯數字,或許外國人可能不懂中文的一加一等於二,中國人可能不懂one plus one equal two ,但是你寫1+1=2,全世界人都知道,只是表達方式有出入。

vdom也是如此,vdom的真正價值就在這裡,為我們提供了一個完美的抽象層,使得我們可以用同一種語言,編寫各個平台都能運行的代碼!

四)有可能…

或許可能vdom會變成標準,但是vdom性能真的不咋地,相對於框架的底層,瀏覽器api來說,與其跑去搞個vdom標準,不如優化其引擎算了……


瀏覽器要是把什麼功能都給你做好了,你的工資也就下降了。

附加一句:還當毛線程序員?


VDOM效率再高也比不上原生DOM吧


vdom 提高性能只是在於你對當前可變化的數據 dom 節點進行 diff/patch 渲染 ,然而實際是大多數頁面似乎都是服務端吐出後就ok了,可能少量的頁面會觸發一些 dom 的 class 變化,這個時候你會發現你構建一個 state 都是浪費。


vdom 不是提高性能,是因為react太好性能了,對他做的優化。。


首先,virtual dom本身就是對平台的DOM的一層抽象,本質上就是一堆javascript對象,提高性能這一說法主要是針對在patch的過程中提高了效率(也就是重新比對進行渲染),而不是在首次渲染的時候提升性能。

因為virtual dom需要創建一堆對象,然後再根據這些對象渲染到瀏覽器上,在這時候其性能其實遠遠不如傳統的字元串拼接的。

virtual dom對平台DOM進行封裝以後,通過一層適配層來操作DOM,其實也實現了跨平台的可能。平台只需要提供對應操作的介面,而不是更上層卻職能更單一的封裝。


推薦閱讀:

Angular2 相比 Vue 有什麼優勢?
angular+meteor 已經有團隊在做,Vue+meteor有類似的項目嗎?
vue中,組件怎麼做到按需載入呢?

TAG:JavaScript | React | Vuejs | 虛擬DOM |