怎么更好的理解虛擬DOM?


雖然Virtual DOM確實是性能杠杠的,但是其實可以說它是無心插柳的一個結果。

React的核心思想:
一個Component拯救世界,忘掉煩惱,從此不再操心界面。

1. Virtual Dom快,有兩個前提
1.1 Javascript很快
Chrome剛出來的時候,在Chrome里跑Javascript非常快,給了其它瀏覽器很大壓力。而現在經過幾輪你追我趕,各主流瀏覽器的Javascript執行速度都很快了。
Julia有一個Benchmark,Julia Benchmarks, 可以看到Javascript跟C語言很接近了,也就幾倍的差距,跟Java基本也是一個量級。
所以說,單純的Javascript其實速度是很快的。
多說一句,這種benchmark並不是絕對的依據,因為用這個語言寫這個跑得快,並不代表一定是用這個語言寫那個也跑得快。

1.2 DOM很慢
關於什么CSS,什么layout那些我不懂,就不瞎說了,咱就說說DOM的結構。
當你用document.createElement()創建一個空的Element的時候(比如創建一個空的div),有以下這幾頁的東西需要實現(當然,這不是標准,只是個大概的意思):
HTMLElement - Web API Interfaces
Element - Web API Interfaces
GlobalEventHandlers
非常非常多,並且還有不少嵌套引用。
你可以在Chrome console里手動調用document.createElement 然后插入DOM里看看效果。
這還是一個空的Elemnt,啥內容也沒有,就這么復雜。所以說DOM的操作非常慢是可以理解的。不是瀏覽器不想好好實現DOM,而是DOM設計得太復雜,沒辦法。

而更糟糕的是,我們(以及很多框架)在調用DOM的API的時候做得不好,導致整個過程更加的慢。React的Virtual Dom解決的是這一部分問題,它並不能解決DOM本身慢的問題。
比如說,現在你的list是這樣,
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
你想把它變成這樣
<ul>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
</ul>
通常的操作是什么?
先把0, 1,2,3這些Element刪掉,然后加幾個新的Element 6,7,8,9,10進去,這里面就有4次Element刪除,5次Element添加。
而React會把這兩個做一下Diff,然后發現其實不用刪除0,1,2,3,而是可以直接改innerHTML,然后只需要添加一個Element(10)就行了,這樣就是4次innerHTML操作加1個Element添加,比9次Element操作快多了吧?

當然還有其它一些例子能夠優化我們對DOM的操作,就不舉例子了。(實際上是因為我舉不出例子。。。)

2. 關於React
2.1 接口和設計
在React的設計里,是完全不需要你操作DOM的。在React里其實根本就沒有DOM這個概念的存在,只有Component。當你寫好一個Component以后,Component會完全負責UI,你不需要也不應該去也不能夠指揮Component怎么顯示,你只能告訴它你想要顯示一個香蕉還是兩個梨。
隔離DOM並不是因為DOM慢(當然DOM確實慢),而是把界面和業務完全隔離,操作數據的只關心數據,操作界面的只關心界面。可以想象成把MVC里面的Controller分成兩個部分,一部分合並到M里面去,一部分合並到V里面去,就剩下MV,沒有C了。。。其實M也並不是Model了。推薦看一下Pete Hunt的這個Talk 

重復一遍,React的意思是,我提供一個Component,然后你只管給我數據,界面的事情完全不用你操心,我保證會把界面變成你想要的樣子。
你可以把一個React的Component想象成一個Pure Function,只要你給的數據是[1, 2, 3],我保證顯示的是[1, 2, 3]。沒有什么刪除一個Element,添加一個Element這樣的事情。NO。你要我顯示什么就給我一個完整的列表。

說到這里,插一句別的,我一開始看到這里還以為這樣的處理方式比較適合一般的WEB應用,寫游戲啊什么的可能這個模式不太好用,然后我就看到Pete Hunt那個Talk,說DOOM 3就是這么干的。

。。
。。。
眼淚都下來了,大神們的思路果然我是摸不着邊的,洗洗睡吧。

睡醒了接着說。React其實需要從Imperative Programming轉換到Declarative Programming去理解。你不要一步一步告訴我這件事情怎么做,什么先和面再剁餡,NO,告訴我你想要煎餅還是月餅,我會想辦法去做的,不要來干擾我。你只需要告訴我有這么一個列表[1, 3, 6]需要顯示就行了,不要告訴我怎么顯示,我會想辦法的,我保證美得冒泡,各種神奇的效果,亮瞎你的鈦合金狗眼。

行了行了,你真啰嗦。

。。。

再說幾句瞎扯的話,Flux雖然說的是單向的Data Flow,但是實際上就是單向的Observer。
Store->View->Action->Store(箭頭是數據流向,實現上可以理解為View監聽Store,View直接trigger action,然后Store監聽Action)
等等,不是說Component是pure function不跟誰綁定嗎,為啥View要監聽Store?你這個騙子。怪不得都沒有人給你點贊。
。。。
。。

我們還是繼續說React把,Flux是什么鬼,我反正沒聽過。


2.2 實現
OK,那么,如何實現React呢?
其實對於React來說,最容易實現的辦法是每次完全摧毀整個DOM,然后重新建立一個全新的DOM。因為一個Component是一個Pure function,根本就沒有State這個概念,我又不知道DOM現在是什么樣子,那最簡單的辦法當然是只要你給新數據,我就把整個DOM刪了,然后根據你給的數據重新生成一個DOM咯。

等等,Virtual DOM哪兒去了?

事實是這樣的,最簡單實現React的方式雖然說非常簡單,但是效率實在是太低了,你居然要全部都刪了重建DOM,DOM本身已經很慢了,你還這么去用,誰能忍啊?

然后Virtual DOM就來救場了。

Virtual DOM和DOM是啥關系呢?
首先,Virtual DOM並沒有完全實現DOM,Virtual DOM最主要的還是保留了Element之間的層次關系和一些基本屬性。因為DOM實在是太復雜,一個空的Element都復雜得能讓你崩潰,並且幾乎所有內容我根本不關心好嗎。所以Virtual DOM里每一個Element實際上只有幾個屬性,並且沒有那么多亂七八糟的引用。所以哪怕是直接把Virtual DOM刪了,根據新傳進來的數據重新創建一個新的Virtual DOM出來都非常非常非常快。(每一個component的render函數就是在做這個事情,給新的virtual dom提供input)

所以,引入了Virtual DOM之后,React是這么干的:
你給我一個數據,我根據這個數據生成一個全新的Virtual DOM,然后跟我上一次生成的Virtual DOM去 diff,得到一個Patch,然后把這個Patch打到瀏覽器的DOM上去。完事。

有點像版本控制打patch的思路。
假設在任意時候有,VirtualDom1 == DOM1 (組織結構相同)
當有新數據來的時候,我生成VirtualDom2,然后去和VirtualDom1做diff,得到一個Patch。
然后將這個Patch去應用到DOM1上,得到DOM2。
如果一切正常,那么有VirtualDom2 == DOM2。

這里你可以做一些小實驗,去破壞VirtualDom1 == DOM1這個假設(手動在DOM里刪除一些Element,這時候VirtualDom里的Element沒有被刪除,所以兩邊不一樣了)。
然后給新的數據,你會發現生成的界面就不是你想要的那個界面了。


最后,回到為什么Virtual Dom快這個問題上。
其實是由於每次生成virtual dom很快,diff生成patch也比較快,而在對DOM進行patch的時候,我能夠根據Patch的內容,優化一部分DOM操作,比如之前1.2里的那個例子。
重點就在最后,哪怕是我生成了virtual dom,哪怕是我跑了diff,但是我根據patch簡化了那些DOM操作省下來的時間依然很可觀。所以總體上來說,還是比較快。

簡單發散一下思路,如果哪一天,DOM本身的已經操作非常非常非常快了,並且我們手動對於DOM的操作都是精心設計優化過后的,那么加上了VirtualDom還會快嗎?
當然不行了,畢竟你多做了這么多額外的工作。

但是那一天會來到嗎?
誒,大不了到時候不用Virtual DOM。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM