深入理解虛擬 DOM,它真的不快

大家好,我是桃翁,這裏是前端桃園,一個有溫度的前端公衆號。


前兩天發了一篇別再說 React 快了,要被打臉的,有些人一看到標題就開始噴了,有數據支撐嗎?你的意思就是 diff 算法慢咯?等等等,對於這種人我也只能呵呵了,文章都沒看,就開始噴,槓精怎麼來的,就是這麼來的。

這兩天我在大漠老師的網站上看到這麼一篇文章,感覺寫得更好,所以轉載過來,再次理解一下虛擬 DOM,這裏給大漠老師的 w3cplus 打個廣告,裏面的文章都很好,希望能經常去逛逛。

正文:

使用過React的同學對於Virtual DOM並不陌生,作爲React的重要核心概念,Virtual DOM憑藉其高效的diff算法,讓我們不用關心應用的性能問題,毫無顧忌地修改各種數據狀態。在實際的開發中,我們並不需要關注Virtual DOM在一個框架中是如何運行的,但是理解Virtual DOM的實現原理卻是非常有必要的,同時也有助於我們更加深入React。

前端應用狀態管理

在日益複雜的前端應用中,狀態管理是一個經常被提及的話題,從早期的刀耕火種時代到jQuery,再到現在流行的MVVM時代,狀態管理的形式發生了翻天覆地的變化,我們再也不用維護茫茫多的事件回調、監聽來更新視圖,轉而使用使用雙向數據綁定,只需要維護相應的數據狀態,就可以自動更新視圖,極大提高開發效率。

但是,雙向數據綁定也並不是唯一的辦法,還有一個非常粗暴有效的方式:一旦數據發生變化,重新繪製整個視圖,也就是重新設置一下innerHTML。這樣的做法確實簡單、粗暴、有效,但是如果只是因爲局部一個小的數據發生變化而更新整個視圖,性價比未免太低了,而且,像事件,獲取焦點的輸入框等,都需要重新處理。所以,對於小的應用或者說局部的小視圖,這樣處理完全是可以的,但是面對複雜的大型應用,這樣的做法不可取。

說到這裏,你會說這跟Virtual DOM有什麼關係呢?其實Virtual DOM就是這麼做的,只是在高效的diff算法計算下,避免對整棵DOM樹進行變更,而是進行針對性的視圖變更,將效率做到最優化。

什麼是Virtual DOM

Virtual DOM的概念有很多解釋,從我的理解來看,主要是三個方面,分別是:一個對象,兩個前提,三個步驟。

一個對象指的是Virtual DOM是一個基本的JavaScript對象,也是整個Virtual DOM樹的基本。

兩個前提分別是JavaScript很快和直接操作DOM很慢,這是Virtual DOM得以實現的兩個基本前提。得益於V8引擎的出現,讓JavaScript可以高效地運行,在性能上有了極大的提高。直接操作DOM的低效和JavaScript的高效相對比,爲Virtual DOM的產生提供了大前提。

三個步驟指的是Virtual DOM的三個重要步驟,分別是:生成Virtual DOM樹、對比兩棵樹的差異、更新視圖。這三個步驟的具體實現也是本文將簡述的一大重點。

Virtual DOM三板斧

下面就將介紹Virtual DOM的三個步驟具體的含義以及實現思路。著作權歸作者所有。

  1. 生成 Virtual DOM 樹

DOM是前端工程師最常接觸的內容之一,一個DOM節點包含了很多的內容,但是一個抽象出一個DOM節點卻只需要三部分:節點類型,節點屬性、子節點。所以圍繞這三個部分,我們可以使用JavaScript簡單地實現一棵DOM樹,然後給節點實現渲染方法,就可以實現虛擬節點到真是DOM的轉化。

  1. 對比兩棵樹的差異

比較兩棵DOM樹的差異是Virtual DOM算法最核心的部分,這也是我們常說的的 Virtual DOM的diff算法。在比較的過程中,我們只比較同級的節點,非同級的節點不在我們的比較範圍內,這樣既可以滿足我們的需求,又可以簡化算法實現。著作權歸作者所有。

比較“樹”的差異,首先是要對樹進行遍歷,常用的有兩種遍歷算法,分別是深度優先遍歷和廣度優先遍歷,一般的diff算法中都採用的是深度優先遍歷。對新舊兩棵樹進行一次深度優先的遍歷,這樣每個節點都會有一個唯一的標記。在遍歷的時候,每遍歷到一個節點就把該節點和新的樹的同一個位置的節點進行對比,如果有差異的話就記錄到一個對象裏面。著作權歸作者所有。 商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

例如,上面的div和新的div有差異,當前的標記是0,那麼:patches[0] = [{difference}, {difference}, …]。同理p是patches[1],ul是patches[3],以此類推。這樣當遍歷完整棵樹的時候,就可以獲得一個完整的差異對象。

在這個差異對象中記錄了有改變的節點,每一個發生改變的內容也不盡相同,但也是有跡可循,常見的差異包括四種,分別是:

  • 替換節點
  • 增加/刪除子節點
  • 修改節點屬性
  • 改變文本內容

所以在記錄差異的時候要根據不同的差異類型,記錄不同的內容。

更新視圖

在第二步得到整棵樹的差異之後,就可以根據這些差異的不同類型,對DOM進行針對性的更新。與四種差異類型相對應的,是更新視圖時具體的更新方法,分別是:

  • replaceChild()
  • appendChild()/removeChild()
  • setAttribute()/removeAttribute()
  • textContent

動手實現Virtual DOM

對原理有了一定的認識之後,自然是動手實現一番了,GitHub上有很多對Virtual DOM的實現,比如simple-virtual-dom(https://github.com/livoras/simple-virtual-dom/)、virtual-dom (https://github.com/Matt-Esch/virtual-dom)等,我也對其進行了一個基本的實現,比較簡陋,傳送門(https://github.com/y8n/simple-virtual-dom)。

進一步思考

Virtual DOM的原理和實現的說明已經結束了,但是對於Virtual DOM的思考遠沒有結束,Virtual DOM 對前端開發的影響難道就只是一堆算法嗎?

  1. 性能對比

首先,先來看一下性能,在諸多的Virtual DOM實現中,都會強調算法的高效,那麼在實際的使用中,Virtual DOM的性能到底如何呢?

上圖是對一個簡單的DOM樹進行不同方式的操作,由左邊的結構更新爲右邊的結構,通過原生操作、jQuery、Virtual DOM和React四種方式,在Chrome的timeline中得到的性能對比,在這個圖中,我們並沒有看出Virtual DOM或者React的優勢,通過對比我們發現,原生的操作要比其他三種方式快,而其他三種方式就相差無幾了。當然,這樣一個簡單測試並沒有說明什麼,測試的DOM結構簡單,和我們平時面對的業務場景不是一個量級,代表不了什麼,但是起碼我們可以看到,這種情況下好像Virtual DOM並沒有我們想象的性能優勢。

在接下來的測試中我們增加測試量。上圖分別是使用原生操作、Virtual DOM和React三種方式進行兩類測試:插入10000個節點100次和修改3000個節點的屬性100次。分別取這100次的耗時最大值、最小值和平均值。從圖中我們可以看到明顯的差異,Virtual DOM和React的差異可以理解,畢竟我們自己實現的Virtual DOM沒有那麼龐大,只是針對虛擬DOM而實現的,比React快一點可以理解,但是原生的操作比Virtual DOM和React都要快得多,這又是怎麼一回事,好像和我們預想的不一樣,回到最初,我們提到,Virtual DOM的產生前提之一就是直接操作DOM很慢,現在看來直接操作不但不慢,反而快了很多,這不得不讓我產生了懷疑,是我對Virtual DOM的理解有誤還是對DOM的理解有誤呢?

再次審視Virtual DOM

框架存在的意義是什麼?是提高性能?提高開發效率?亦或是其他用途,每個人對框架的理解不同,答案也不盡相同。但是不得不承認,存在框架的情況下,項目的可維護性有了極大的提高,而對於其他方面就要做出犧牲,比如性能。在上面的性能測試中,其實完全走入了一個誤區,在測試中我們用到的原生的操作其實是“人爲”地對操作進行優化之後的結果,而如果拋開人爲優化的前提,最終的結果可能就不是這樣了。Virtual DOM的優勢不在於單次的操作,而是在大量、頻繁的數據更新下,能夠對視圖進行合理、高效的更新。這一點是原生操作遠遠無法替代的。

到此爲止,再次審視Virtual DOM,可以簡單得出如下結論:

  • Virtual DOM 在犧牲部分性能的前提下,增加了可維護性,這也是很多框架的通性
  • 實現了對DOM的集中化操作,在數據改變時先對虛擬DOM進行修改,再反映到真實的DOM中,用最小的代價來更新DOM,提高效率
  • 打開了函數式UI編程的大門
  • 可以渲染到DOM以外的端,比如ReactNative

結語

本文對Virtual DOM有一個簡單的介紹,包括實現的部分也很簡單,甚至對列表的diff算法也偷工減料,跟多高級的特性也沒有涉及,比如事件綁定、生命週期、JSX語法等,如果加上這些內容,就是一個小型版的React了。

本文旨在讓大家瞭解並認識 Virtual DOM 的基本概念、組成和實現,同時對Virtual DOM更深層的意義有所瞭解,這樣在以後用到相關的框架的時候也不會兩眼一抹黑了,起碼在性能優化上有點認識,比如列表要帶上key這樣基本的優化操作。

都看到這兒了,點個贊再走吧! 另外再說一句,當遇到顛覆自己認知的時候,不要總覺得你自己的是對的,很多時候你一直以爲都是錯的,從你最開始學這個的時候。這個時候我們不要做槓精,應該做一個探索者,好好看看別人的文章是否有道理,感覺還是不能說服自己就去網上查資料,再好好審視一下,別人爲什麼要這麼說。 不要做槓精、不要做槓精、不要做槓精


原文鏈接:https://www.w3cplus.com/javascript/understand-the-Virtual-DOM.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章