Vue 為什麼要用虛擬 DOM(Virtual DOM)

jinzhuming發表於2020-10-10

背景

依舊接著說面試那點事,最近面試了很多人,面對曾經用過 vue 的候選人,我都會問一個問題:Vue 為什麼要在 2.x 之後切換到 虛擬 DOM 上?
而在這個過程中我發現了很多人都會陷入一個誤區:** 選擇一個技術一定是因為這個技術是好的、優秀的,所以才會用它。** 這個理解實際上也並不算錯,但是我認為也是不妥的,很多時候選擇一個技術真不是因為它多好,而是因為它能解決你當前碰到的問題,所以哪怕它並不完美,甚至並不好,可是隻要能解決問題,並且暫時並沒有其它更好的解決方案,同時帶來的後果能在承受範圍之內,那麼就可以採用它。很顯然,虛擬 DOM 就是一個這樣的東西。


虛擬 DOM 真的快嗎?

非常多的人在說到虛擬 DOM 都會說虛擬 DOM 的 diff 演算法如何如何,怎麼提升效能如何如何,但事實上只要仔細思考一個問題你就能知道這個結論是站不住腳的,至少在相當多情況下是站不住腳的:虛擬 DOM 透過 diff 演算法找到了發生變化後的節點,它是不是依舊要操作 真實 DOM 修改?那麼它憑什麼就比直接修改 真實 DOM 更快呢?(顯然,它多了一層 diff 操作)。
當然很多人會提到,虛擬 DOM 可以批次在記憶體操作,然後把結果輸出到瀏覽器,大量修改的情況下效能更優秀,可以保證大量操作下的效能下限,真實操作 DOM 每次都會引起重繪,不如 虛擬 DOM 批次替換效率高。看起來確實是這樣,可是事實上呢?首先,並不會有人無聊到真的重複更新大批次資料吧?為了一個極少出現的場景放棄了大多數的場景,值得麼?相反,大多數正常操作下,本來只需要修改兩個簡單的 DOM,而 虛擬 DOM 還要再完整走一遍 diff 演算法,才找到這兩個節點,再去替換,這是否又拉低了效能呢?更何況 虛擬 DOM 一直有一個解決不了的問題就是:他根本不知道什麼時候該更新,什麼時候不該更新,也是因為這個原因,React 才提供了 ShouldComponentUpdate 讓使用者自己控制是否要更新。更更何況,虛擬 DOM 做的這個合併操作,其實手動也是可以操作的,我想做 Canvas 的人應該大有體會,在記憶體裡生成一個虛擬的 Canvas,然後進行多次繪製,之後一次性將這個 Canvas 渲染到頁面上。事實上,React 官方並不是很在意這個效能,比如 React 自己的 虛擬 DOM 演算法都不是最優秀的(虛擬 DOM 的演算法相當多),隨便列舉幾個:

其中 inferno 的速度更是快得離譜。
這足以說明,React 其實是用 虛擬 DOM 根本不是因為快,或者換個說法,虛擬 DOM 的效能並不是 React 的首要考慮,當然也並不是說完全就不考慮效能,inferno 的作者似乎已經進入 React 團隊。更願意說:虛擬 DOM 只是 React 為了實現目的而選擇的一條最合適的路,他只是手段,而不是目的,他的 DOM 操作最佳化也只是避免效能損耗的手段,而不是 React 的根本目的。
當然這並不是說 虛擬 DOM 就沒有優點,優點也是很明顯的,雖然我們能夠手動合併 DOM,可是這畢竟是一個繁瑣的操作,如果有框架能幫助我們合併,又何必去手動操作呢?在少量 DOM 更新的情況下,即使 虛擬 DOM 會拖累速度,事實上也並不會對體驗有什麼影響,反倒是在大量操作的情況下保障了效能下限,會讓我們的應用可用性更穩定。

Ok,接著回到 VueVue 事實上是有依賴收集這個過程的,換句話說,其實 Vue 在依賴收集階段,完全可以精確的收集到頁面哪些 DOM 使用了資料,並且可以在資料變化的時候精確的更新這些 DOM,這個效能並不會很差,Vue1 一直是這麼幹的,而 Vue1 長期標榜自己的效能會比 React 更加優秀也證明了這點,對於 Vue 而言,似乎直接精準收集依賴,精準更新更加的自然,使用虛擬 DOM 看起來更像是脫褲子放屁:我明明可以知道哪個 DOM 更新,依然要裝作我不知道,然後走一遍 diff 演算法才發現,哦,原來是這幾個 dom 更新了。這裡提一嘴,AtomVsCode 都是 js 製作的,其中 Atom 曾經使用過 虛擬 DOMAtom 是個什麼效能大家都清楚,最終轉為 真實 DOM 操作,VsCode 則是一直使用 真實 DOM,編輯器場景其實和 vue 很像,編輯器當然準確知道你操作了哪一行哪一個文字,但是如果他依舊裝作不知道要走一遍虛擬 dom,這就正如之前說的,完全是脫褲子放屁的無意義操作。所以不要天真地以為 虛擬 DOM 就是快,就是好,就是一定要上的,虛擬 DOM 並不是完全沒有代價的。


虛擬 DOM 是否還有其他優點?

正如之前所說,虛擬 DOM 其實並不見得比手動快,但是他卻提供了一個非常重要的特性:可以接受 Parser 解析轉化。這意味著其實相當多的東西我們都可以在編譯階段解決,比如:xss 攻擊($$typeof)、合併 DOM 操作、跨平臺 等等。
其中我認為最重要的就是跨平臺,平臺不單止 Webios 等,更是止程式語言,比如考慮這樣一個情況:我們是否可以編寫一套轉換器,用來把 python 轉換為 js?這其中比較麻煩的就是 DOM,因為 DOM 是 js 獨有的東西,可是擁有 虛擬 DOM 之後我們可以在 python 也實現一套,之後轉為 虛擬 DOM 這樣一個統一的抽象格式,這樣不就實現了其他程式語言平臺來編寫前端程式碼?事實上 ssr(服務端渲染) 就是這個原理,node 是沒有 DOm 的,透過虛擬 DOM 來抽象即可達到操作 DOM 的目的,同時我們在 jsx 裡使用函式來編寫宣告式程式設計來編寫原本命令式的 DOM 操作也是得益於此。再比如我們是否可以把前端平臺程式碼移植到其他平臺?比如 React NativeWeex,比如數量繁多的小程式框架。


結論

所以我一直認為,虛擬 DOM 的真實意義不是他的效能,而是他提供了無限的可能,他是對 真實 DOM 的抽象,你可以用一門全新的語言寫下程式碼,抽象為 虛擬 DOM,然後再透過社群其他成員編寫完成的 虛擬 DOM 庫,來轉為各種平臺(iosandroid)的頁面,只要有一個框架實現這個轉換,其他框架都可以享受這個紅利。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章