既然用 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只有在重複渲染的時候才可能提高性能,畢竟要多一個運算步驟,也要消耗更多的內存,只渲染一次,不會獲得任何性能的好處。
瀏覽器傳統的渲染方式有兩種:
- 給一個HTML字元串,解析,變成DOM Tree,畫出來;
- 對一個或者一組選中的DOM進行操作。
這兩種操作方式都很原始很底層,正是瀏覽器該做的事情,至於怎麼做單頁應用,怎麼在重複渲染中提高性能,是應用層做的事情。
所以說,瀏覽器發展總是要慢網頁應用一拍啊,好多事情都是應用層先實現了,然後才推動瀏覽器層去實現。
至於瀏覽器能不能實現Virtual DOM,當然能,實際上,已經有這個Proposal了 trueadm/t7 。
【大多數答案都沒答到要點……】
「直接操作 DOM 性能差」是因為 ——
- DOM 引擎、JS 引擎 相互獨立,但又工作在同一線程(主線程)
- JS 代碼調用 DOM API 必須 掛起 JS 引擎、轉換傳入參數數據、激活 DOM 引擎,DOM 重繪後再轉換可能有的返回值,最後激活 JS 引擎並繼續執行
- 若有頻繁的 DOM API 調用,且瀏覽器廠商不做「批量處理」優化,引擎間切換的單位代價將迅速積累
- 若其中有強制重繪的 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,但依次採用了多層優化手段 ——
- 對用於雙向數據綁定、但頻繁觸發的 input 事件,啟用函數節流
- 對每次提交給 ViewModel 的數據做 diff,只用數據差集來做重繪
- 對 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年代,也很常用,但是會有兩個明顯的斷代:
- innerHTML是Devil的年代:那個時代,所有的innerHTML操作都是慢到死,基本上Best Practice就是使用DOM Element進行操作,同時限於OnSite DOM本身操作性能也不高,建立一個Orphan Element,做完各種操作之後(比如Append Child),然後Swap到DOM Tree上去,這是一種常見的高級手段
- innerHTML已經不再低效,甚至比DOM操作還要快的時代:模板化語言開始更多的使用起來,使模板生成DOM再Swap到DOM Tree上,既簡化了開發難度,又提高了動態性, 實際上比傳統的雙向綁定方式安全且高效
再說,「為什麼瀏覽器不直接自帶這個功能呢?」,問這種問題的人,簡直對瀏覽器的歷史太不了解了。
可以說,瀏覽器是計算機中歷史最豐富,並且充滿了波瀾壯闊,絕命廝殺的歷史了。
可以參考這兩個鏈接:
History of the web browser Browser wars - Wikipedia
瀏覽器向來都是黑魔法誕生的地方,並且,瀏覽器的代碼裡面擁有太多了人類智慧的積累和各種妥協了。為什麼現在除了這幾個還活著的瀏覽器渲染引擎,連Google這種公司都不敢自己寫一個渲染引擎了呢?因為太多的兼容性了啊。。。。先不說太多,單是W3C一個組織。
All Standards and Drafts ,請數一下它已經有多少規範了,先不說實現,數一下一個人能否在一生之中有效工作時間裡面,把這些規範的每一個細節理解清楚。
瀏覽器有著比其它程序所想像不到的歷史包袱。有太多奇怪的功能,存在在那裡只是為了某一種歷史黑魔法能夠正常運行了。
所以,回過頭來說,它能不能自帶Virtual DOM的功能呢?
- 它本來就有,我剛剛就有說,Orphan Element就是一種Virtual DOM的概念,它擁有一切 DOM的事件,但是又不會需要動用任何瀏覽器的渲染資源
- 類似React這種技術的Virtual DOM(類Template,但是實際上比Template更強類型,更多語義)呢?這就是我想寫這個回答的主要原因了。
關於這個,我有三點想法:
- 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 |