2016-11-1 藍(lán)藍(lán)設(shè)計(jì)的小編
React 好像已經(jīng)火了很久很久,以致于我們對(duì)于 Virtual DOM 這個(gè)詞都已經(jīng)很熟悉了,網(wǎng)上也有非常多的介紹 React、Virtual DOM 的文章。但是直到前不久我專(zhuān)門(mén)花時(shí)間去學(xué)習(xí) Virtual DOM,才讓我對(duì) Virtual DOM 有了一定的理解,以致于要懷疑起很久之前看過(guò)的那些文章來(lái)。倒不是這些文章講得不對(duì),而是現(xiàn)在在我看來(lái)角度不太好,說(shuō)得越多,越說(shuō)不清。
讓我能夠有所開(kāi)竅(自認(rèn)為)的,是這篇文章:
Change And Its Detection In JavaScript Frameworks
Monday Mar 2, 2015 by Tero Parviainen
作者看問(wèn)題的角度很棒,從數(shù)據(jù)變更與UI同步的角度來(lái)介紹各個(gè)典型框架,特別是對(duì)于 React 的 Virtual DOM,從這個(gè)角度理解起來(lái)更容易些。
感興趣的同學(xué),如果沒(méi)有讀過(guò)這篇文章,推薦去看一看,不感興趣就算了。不過(guò)接下來(lái)我要講的東西,部分整理自這篇文章,特別是從這篇文章中引用的圖片,非常棒。當(dāng)然還有我自己的一些思考,以及一些對(duì)于目前 Virtual DOM 實(shí)現(xiàn)的開(kāi)源庫(kù)的分析。
如果讀了上面推薦的這篇文章,我倒是不介意你不再繼續(xù)把本文讀下去,因?yàn)橛行〇|西你已經(jīng)領(lǐng)會(huì)到了。當(dāng)然,也不反對(duì)。
談?wù)擁?yè)面的變化之前,咱們先看下數(shù)據(jù)和頁(yè)面(視覺(jué)層面的頁(yè)面)的關(guān)系。數(shù)據(jù)是隱藏在頁(yè)面底下,通過(guò)渲染展示給用戶(hù)。同樣的數(shù)據(jù),按照不同的頁(yè)面設(shè)計(jì)和實(shí)現(xiàn),會(huì)以不同形式、樣式的頁(yè)面呈現(xiàn)出來(lái)。有時(shí)候在一個(gè)頁(yè)面內(nèi)的不同位置,也會(huì)有相同數(shù)據(jù)的不同表現(xiàn)。
Web 的早期,這些頁(yè)面通常是靜態(tài)的,頁(yè)面內(nèi)容不會(huì)變化。而如果數(shù)據(jù)發(fā)生了變化,通常需要重新請(qǐng)求頁(yè)面,得到基于新的數(shù)據(jù)渲染出的新的頁(yè)面。
至少,這個(gè)模式理解起來(lái)挺簡(jiǎn)單不是嗎。
直到 Web 應(yīng)用復(fù)雜起來(lái),開(kāi)發(fā)者們開(kāi)始關(guān)注用戶(hù)體驗(yàn),開(kāi)始將大量的處理向前端遷移,頁(yè)面變得動(dòng)態(tài)、靈活起來(lái)。一個(gè)顯著的特征是,數(shù)據(jù)發(fā)生變化之后,不再需要刷新頁(yè)面就能看到頁(yè)面上的內(nèi)容隨之更新了。
前端需要做的事情變得多了起來(lái),前端工程師們也就修煉了起來(lái),各種前端技術(shù)也就出現(xiàn)了。
首先,聰明的工程師們發(fā)現(xiàn)既然是在前端渲染頁(yè)面,如果只是部分?jǐn)?shù)據(jù)發(fā)生了變化,就要把頁(yè)面整體或一大塊區(qū)域重新渲染就有點(diǎn)笨了。為什么不把事情做得更些,只更新變化的數(shù)據(jù)對(duì)應(yīng)的頁(yè)面的內(nèi)容呢?
怎么做呢?操作 DOM 唄。DOM 就是瀏覽器提供給開(kāi)發(fā)者用于操作頁(yè)面的模型嘛,直接通過(guò)腳本來(lái)調(diào)用 DOM 的各種接口就 OK 了。而且我們還有了像 jQuery 這樣的棒棒的工具,操作 DOM 變得 so easy。
然而,頁(yè)面越來(lái)越復(fù)雜,聰明的工程師們發(fā)現(xiàn)數(shù)據(jù)變化之后,老是需要手動(dòng)編碼去操作對(duì)應(yīng)的 DOM 節(jié)點(diǎn)執(zhí)行更新,有點(diǎn)煩,不夠懶啊。于是各種框架如雨后春筍般出現(xiàn)了,紛紛表示可以簡(jiǎn)化這個(gè)過(guò)程。
稍微早期的框架有這樣的:
開(kāi)發(fā)者借助框架,監(jiān)聽(tīng)數(shù)據(jù)的變更,在數(shù)據(jù)變更后更新對(duì)應(yīng)的 DOM 節(jié)點(diǎn)。雖然還是要寫(xiě)一些代碼,但是寫(xiě)出來(lái)的代碼好像很有條理的樣子,至少更容易理解和維護(hù)了,也不錯(cuò)嘛。
更進(jìn)一步,MVVM 框架出現(xiàn)了,以 AngularJS 為代表:
仍然是數(shù)據(jù)變化后更新對(duì)應(yīng) DOM 節(jié)點(diǎn)的方式,但是建立這種綁定關(guān)系的過(guò)程被框架所處理,開(kāi)發(fā)者要寫(xiě)的代碼變少了,而且代碼更易讀和維護(hù)了。
再然后呢,大家就在這個(gè)棒棒的模式上繼續(xù)深耕,紛紛表示還可以在性能上做得更好,前端領(lǐng)域一片繁榮。
再后來(lái) React 出現(xiàn)了,它不僅不是 MVVM 框架,甚至連 MV 框架都不是。這年頭,不是個(gè) MV 框架還好意思出門(mén)?可 React 還真的帶來(lái)了新的思路!
什么思路呢?
就是回到過(guò)去,回到那個(gè)簡(jiǎn)單而美好的時(shí)候。具體而言,就是每次數(shù)據(jù)發(fā)生變化,就重新執(zhí)行一次整體渲染。的確這樣更簡(jiǎn)單,不用去琢磨到底是數(shù)據(jù)的哪一部分變化了,需要更新頁(yè)面的哪一部分。但是壞處太明顯,體驗(yàn)不好啊。而 React 給出了解決方案,就是 Virtual DOM。
Virtual DOM 概況來(lái)講,就是在數(shù)據(jù)和真實(shí) DOM 之間建立了一層緩沖。對(duì)于開(kāi)發(fā)者而言,數(shù)據(jù)變化了就調(diào)用 React 的渲染方法,而 React 并不是直接得到新的 DOM 進(jìn)行替換,而是先生成 Virtual DOM,與上一次渲染得到的 Virtual DOM 進(jìn)行比對(duì),在渲染得到的 Virtual DOM 上發(fā)現(xiàn)變化,然后將變化的地方更新到真實(shí) DOM 上。
簡(jiǎn)單來(lái)說(shuō),React 在提供給開(kāi)發(fā)者簡(jiǎn)單的開(kāi)發(fā)模式的情況下,借助 Virtual DOM 實(shí)現(xiàn)了性能上的優(yōu)化,以致于敢說(shuō)自己“不慢”。
React 基于 Virtual DOM 的數(shù)據(jù)更新與UI同步機(jī)制:
初始渲染時(shí),首先將數(shù)據(jù)渲染為 Virtual DOM,然后由 Virtual DOM 生成 DOM。
數(shù)據(jù)更新時(shí),渲染得到新的 Virtual DOM,與上一次得到的 Virtual DOM 進(jìn)行 diff,得到所有需要在 DOM 上進(jìn)行的變更,然后在 patch 過(guò)程中應(yīng)用到 DOM 上實(shí)現(xiàn)UI的同步更新。
Virtual DOM 作為數(shù)據(jù)結(jié)構(gòu),需要能準(zhǔn)確地轉(zhuǎn)換為真實(shí) DOM,并且方便進(jìn)行對(duì)比。除了 Virtual DOM 外,React 還實(shí)現(xiàn)了其他的特性,為了專(zhuān)注于 Virtual DOM,我另外找了兩個(gè)比較 Virtual DOM 來(lái)學(xué)習(xí):
這里也推薦給感興趣且還沒(méi)有讀過(guò)兩個(gè)庫(kù)源碼的同學(xué)。
由于只關(guān)注 Virtual DOM,通過(guò)閱讀兩個(gè)庫(kù)的源碼,對(duì)于 Virtual DOM 的定位有了更深一步的理解。
首先看數(shù)據(jù)結(jié)構(gòu)。
Virtual DOM 數(shù)據(jù)結(jié)構(gòu)
DOM 通常被視為一棵樹(shù),元素則是這棵樹(shù)上的節(jié)點(diǎn)(node),而 Virtual DOM 的基礎(chǔ),就是 Virtual Node 了。
在 virtual-dom 中,給 Virtual Node 聲明了對(duì)應(yīng)的類(lèi) VirtualNode,基本是用于存儲(chǔ)數(shù)據(jù),包括:
Snabbdom 的 Virtual Node 則是純數(shù)據(jù)對(duì)象,通過(guò) vnode 模塊來(lái)創(chuàng)建,對(duì)象屬性包括:
雖然有所差別,除去實(shí)現(xiàn)上的差別和庫(kù)本身的額外特性,可以看到 Virtual Node 用于創(chuàng)建真實(shí)節(jié)點(diǎn)的數(shù)據(jù)包括:
有了這些其實(shí)就可以創(chuàng)建對(duì)應(yīng)的真實(shí)節(jié)點(diǎn)了。
創(chuàng)建 Virtual DOM
嵌套 Virtual Node 就可以得到一棵樹(shù)了。virtual-dom 和 Snabbdom 都提供了函數(shù)調(diào)用的方式來(lái)創(chuàng)建 Virtual Tree,這個(gè)過(guò)程就是渲染了:
React 提供 JSX 這顆糖,使得我們可以用類(lèi)似 HTML 的語(yǔ)法來(lái)編寫(xiě),不過(guò)編譯后實(shí)質(zhì)還是通過(guò)函數(shù)調(diào)用來(lái)得到一棵嵌套的 Virtual Tree。而且這對(duì)于理解 Virtual DOM 機(jī)制來(lái)說(shuō)不是特別重要,先不管這個(gè)。
使用 Virtual DOM
首先來(lái)看初始化,virtual-dom 提供了 createElement 函數(shù):
再來(lái)看更新。virtual-dom 有明確的兩步操作,首先 diff,然后 patch:
而 Snabbdom 則簡(jiǎn)單些,只有一個(gè) patch 函數(shù),內(nèi)部在進(jìn)行比對(duì)的同時(shí)將更新應(yīng)用到了真實(shí) DOM 上,而且初始化也是用的 patch 函數(shù):
性能優(yōu)化
關(guān)于性能優(yōu)化,除了 Virtual DOM 機(jī)制本身提供的特性以外,再就是不同的 Virtual DOM 庫(kù)自身的優(yōu)化方案了,這個(gè)可以看上面兩個(gè)庫(kù)的文檔,不再贅述。
其實(shí)提到 Virtual DOM 的差異比對(duì),有人會(huì)對(duì)其內(nèi)部如何處理數(shù)組感興趣。的確,如果數(shù)組元素的位置發(fā)生了改變,這個(gè)要識(shí)別起來(lái)是有點(diǎn)麻煩。為此,上面兩個(gè)庫(kù)和 React 其實(shí)都在 Virtual Node 上額外記錄了一個(gè)屬性“key”,就是用來(lái)輔助進(jìn)行 Virtual Node 的比對(duì)的。
簡(jiǎn)單來(lái)說(shuō),如果兩個(gè) Virtual Node 的位置不同,但是 key 屬性相同,那么會(huì)將這兩個(gè)節(jié)點(diǎn)視為由相同數(shù)據(jù)渲染得到的,然后進(jìn)一步進(jìn)行差異分析。所以,并不是僅僅按照位置進(jìn)行比對(duì),具體的實(shí)現(xiàn)可以查看各個(gè)庫(kù)的源碼。
OK,以上就是我要講的全部所有內(nèi)容了。
相信很多同學(xué)之前對(duì) Virtual DOM 已經(jīng)很熟悉了,比我理解得更深入的同學(xué)相信也不會(huì)少。不過(guò)從“數(shù)據(jù)變化與UI同步更新”這個(gè)角度來(lái)理解 Virtual DOM,在我看來(lái)是比較好的,所以整理在這里了。
有個(gè)問(wèn)題挺常見(jiàn),AngularJS 和 React 哪個(gè)更好?
如果說(shuō)各有千秋的話(huà),估計(jì)大家就“呵呵”了。但是這兩個(gè)框架/庫(kù)從“數(shù)據(jù)變化與UI同步更新”的角度來(lái)看,的確都解決了問(wèn)題,而且解決問(wèn)題的方式大家都挺認(rèn)可(至少在喜歡它們的同學(xué)眼里是這樣的)。
藍(lán)藍(lán)設(shè)計(jì)( tweetduck.com )是一家專(zhuān)注而深入的界面設(shè)計(jì)公司,為期望卓越的國(guó)內(nèi)外企業(yè)提供卓越的UI界面設(shè)計(jì)、BS界面設(shè)計(jì) 、 cs界面設(shè)計(jì) 、 ipad界面設(shè)計(jì) 、 包裝設(shè)計(jì) 、 圖標(biāo)定制 、 用戶(hù)體驗(yàn) 、交互設(shè)計(jì)、 網(wǎng)站建設(shè) 、平面設(shè)計(jì)服務(wù)
藍(lán)藍(lán)設(shè)計(jì)的小編 http://tweetduck.com