(轉(zhuǎn)載)
主流框架分析
我們看一下現(xiàn)有的一些主流框架從少到多所解決的問題。這個(gè)多少并不是來評(píng)價(jià)框架的好壞横浑,而是從設(shè)計(jì)的角度出發(fā)看它涵蓋多少內(nèi)容。
純模板引擎:最少的就是純模板引擎,只管狀態(tài)到界面的映射睦柴。
React和Vue:其實(shí)這兩者都是非常專注的只做狀態(tài)到界面映射,以及組件毡熏。
Backbone:它會(huì)給你多一些架構(gòu)上指導(dǎo)坦敌,比如它會(huì)讓你分層。
Angular:它做的事情就更多招刹,它有自己的路由恬试,這些都會(huì)包含在里面。
Ember:相比Angular疯暑,Ember做得就更加徹底训柴,Ember信奉的是約定優(yōu)于配置,它會(huì)將一切都幫你設(shè)計(jì)好打包好妇拯,你就開箱用就可以了幻馁。
Meteor:Meteor只是一個(gè)極端,它是從前到后全都包含越锈,從前端到數(shù)據(jù)層到數(shù)據(jù)庫仗嗦,全都幫你打包好。
通過簡(jiǎn)單的分析甘凭,我們可以感受到稀拐,做得少的框架不一定就不如做得多的框架,這體現(xiàn)出一種取舍丹弱。也就是說德撬,做得少的框架可以給你更多的靈活性,但你需要做更多的選擇躲胳;做得多的框架有更強(qiáng)的侵入性蜓洪,學(xué)習(xí)成本更高,靈活性更低坯苹。一旦選擇了一個(gè)侵入性強(qiáng)的框架隆檀,那么一些小的部分你就沒有機(jī)會(huì)去切換成其他你更想用的方案。
Vue的定位與其他框架的區(qū)別就是漸進(jìn)式的想法,也就是“Progressive”——這個(gè)詞在英文中定義是漸進(jìn)恐仑,一步一步泉坐,不是說你必須一竿子把所有的東西都用上。
Vue的設(shè)計(jì)接下來我們回到之前看的圖:
Vue從設(shè)計(jì)角度來講菊霜,雖然能夠涵蓋這張圖上所有的東西坚冀,但是你并不需要一上手就把所有東西全用上,因?yàn)闆]有必要鉴逞。無論從學(xué)習(xí)角度记某,還是實(shí)際情況,這都是可選的构捡。聲明式渲染和組建系統(tǒng)是Vue的核心庫所包含內(nèi)容液南,而客戶端路由、狀態(tài)管理勾徽、構(gòu)建工具都有專門解決方案滑凉。這些解決方案相互獨(dú)立,你可以在核心的基礎(chǔ)上任意選用其他的部件喘帚,不一定要全部整合在一起畅姊。
Vue的實(shí)現(xiàn)接下來深入講一講這些具體的概念以及Vue在這些概念上具體是做怎樣的實(shí)現(xiàn)。
(1) 聲明式渲染
現(xiàn)在基本所有的框架都已經(jīng)認(rèn)同這個(gè)看法——DOM應(yīng)盡可能是一個(gè)函數(shù)式到狀態(tài)的映射狀態(tài)即是唯一的真相吹由,而Dom狀態(tài)只是數(shù)據(jù)狀態(tài)的一個(gè)映射若未,所有的邏輯應(yīng)該盡量在狀體的層面上去進(jìn)行,當(dāng)狀態(tài)改變的時(shí)候倾鲫,View應(yīng)該是在框架幫助下自動(dòng)更新到合理的狀態(tài)粗合,而不是當(dāng)觀察到數(shù)據(jù)變化后手動(dòng)的選擇元素再命令式改動(dòng)它的屬性下圖是Vue的一個(gè)模板示例,如果沒有用過Vue的話乌昔,可以大概感覺到這是一個(gè)怎樣的概念
其實(shí)隙疚,在模板語法上,Vue跟Angular是比較相似磕道。在Vue1.0里面供屉,模板實(shí)現(xiàn)跟Angular類似,如下圖所示溺蕉,把模板直接做成在瀏覽器里面parse成DOM樹伶丐,然后去遍歷這個(gè)樹,提取其中的各種綁定焙贷。
aa.png
在Vue2.0中,渲染層的實(shí)現(xiàn)做了根本性改動(dòng)贿堰,引入了虛擬DOM
從架構(gòu)上講辙芍,2.0依然寫同樣的模板,在最左邊,Vue2.0跟1.0的模板語法絕大部分是兼容的故硅。Vue的編譯器在編譯模板之后庶灿,會(huì)把這些模板編譯成一個(gè)渲染函數(shù)。而函數(shù)被調(diào)用的時(shí)候就會(huì)渲染并且返回一個(gè)虛擬DOM的樹吃衅。這個(gè)樹非常輕量往踢,它的職責(zé)就是描述當(dāng)前界面所應(yīng)處的狀態(tài)。當(dāng)我們有了這個(gè)虛擬的樹之后徘层,再交給一個(gè)patch函數(shù)峻呕,負(fù)責(zé)把這些虛擬DOM真正施加到真實(shí)的DOM上。
在這個(gè)過程中趣效,Vue有自身的響應(yīng)式系統(tǒng)來偵測(cè)在渲染過程中所依賴到的數(shù)據(jù)來源瘦癌。在渲染過程中,偵測(cè)到的數(shù)據(jù)來源之后跷敬,之后就可以精確感知數(shù)據(jù)源的變動(dòng)讯私。到時(shí)候就可以根據(jù)需要重新進(jìn)行渲染。當(dāng)重新進(jìn)行渲染之后西傀,會(huì)生成一個(gè)新的樹斤寇,將新樹與舊樹進(jìn)行對(duì)比,就可以最終得出應(yīng)施加到真實(shí)DOM上的改動(dòng)拥褂。最后再通過patch函數(shù)施加改動(dòng)娘锁。
這樣做的主要原因是,在瀏覽器當(dāng)中肿仑,JavaScript的運(yùn)算在現(xiàn)代的引擎中非持旅耍快,但DOM本身是非常緩慢的東西尤慰。當(dāng)你調(diào)用原生DOM API的時(shí)候馏锡,瀏覽器需要在JavaScript引擎的語境下去接觸原生的DOM的實(shí)現(xiàn),這個(gè)過程有相當(dāng)?shù)男阅軗p耗伟端。所以杯道,本質(zhì)的考量是,要把耗費(fèi)時(shí)間的操作盡量放在純粹的計(jì)算中去做责蝠,保證最后計(jì)算出來的需要實(shí)際接觸真實(shí)DOM的操作是最少的党巾。
下面看渲染函數(shù)。用過React的開發(fā)者可能知道霜医,React是沒有模板的齿拂,直接就是一個(gè)渲染函數(shù),它中間返回的就是一個(gè)虛擬DOM樹肴敛。JSX實(shí)際就是一套用于讓我們更簡(jiǎn)單地去描述樹狀結(jié)構(gòu)的語法糖如下圖所示署海,在Vue2.0當(dāng)中吗购,可以看到就是說當(dāng)比如左側(cè)的模板,經(jīng)過Vue的編譯之后就會(huì)變成右側(cè)的東西砸狞。
此函數(shù)類似于創(chuàng)建一個(gè)虛擬元素的函數(shù)捻勉,我們可以給它一個(gè)名字,給它描述應(yīng)該有的屬性特性和可能其他的數(shù)據(jù)刀森。然后后面這個(gè)最后這個(gè)參數(shù)是個(gè)數(shù)組踱启,包含了該虛擬元素的子元素⊙械祝總的來說2.0的編譯器做的就是這個(gè)活埠偿。
同時(shí),在Vue2.0里飘哨,用戶可以選擇直接跳過模板這一層去手寫渲染函數(shù)胚想,同時(shí)也有可選JSX支持。從開發(fā)者的偏好以及開發(fā)者的效益的角度來考量芽隆,模板和JSX是各有利弊的東西浊服。模板更貼近我們的HTML,可以讓我們更直觀地思考語義結(jié)構(gòu)胚吁,更好地結(jié)合CSS的書寫牙躺。
JSX和直接渲染函數(shù),因?yàn)槭钦嬲腏avaScript腕扶,擁有這個(gè)語言本身的所有的能力孽拷,可以進(jìn)行復(fù)雜的邏輯判斷,進(jìn)行選擇性的返回最終要返回的DOM結(jié)構(gòu)半抱,能夠?qū)崿F(xiàn)一些在模板的語法限制下脓恕,很難做到的一些事情。
所以在Vue2.0里窿侈,兩個(gè)都是可以選擇的炼幔。在絕大部分情況下使用模板,但是在需要復(fù)雜邏輯的情況下史简,使用渲染函數(shù)乃秀。在Vue2.0的路由和內(nèi)部的一些實(shí)踐上,都大量地應(yīng)用渲染函數(shù)做復(fù)雜的抽象組件圆兵,比如過渡動(dòng)畫組件以及路由里面的link組件跺讯,都是用渲染函數(shù)實(shí)現(xiàn)的,同時(shí)還保留了它本身的依賴追蹤系統(tǒng)殉农。
舉例
在渲染函數(shù)里面用到A.B的時(shí)候刀脏,這個(gè)就會(huì)觸發(fā)對(duì)應(yīng)的 getter。整個(gè)渲染流程具體要點(diǎn)如下:
當(dāng)某個(gè)數(shù)據(jù)屬性被用到時(shí)超凳,觸發(fā) getter愈污,這個(gè)屬性就會(huì)被作為依賴被 watcher 記錄下來危队。
整個(gè)函數(shù)被渲染完的時(shí)候,每一個(gè)被用到的數(shù)據(jù)屬性都會(huì)被記錄钙畔。
相應(yīng)的數(shù)據(jù)變動(dòng)時(shí),例如給它一個(gè)新的值金麸,就會(huì)觸發(fā) setter擎析,通知數(shù)據(jù)對(duì)象對(duì)應(yīng)數(shù)據(jù)有變化。
此時(shí)會(huì)通知對(duì)應(yīng)的組件挥下,其數(shù)據(jù)依賴有所改動(dòng)揍魂,需要重新渲染。
對(duì)應(yīng)的組件再次調(diào)動(dòng)渲染函數(shù)棚瘟,生成 Virtual DOM现斋,實(shí)現(xiàn) DOM 更新。
這樣一個(gè)流程跟主流的一些框架偎蘸,例如React是有較大區(qū)別的庄蹋。在React中,當(dāng)組件復(fù)雜的時(shí)候需要用 shouldComponentUpdate 做優(yōu)化迷雪。但是限书,它也有自己的各種坑,比如要確保該組件下面的組件不依賴外部的狀態(tài)章咧。雖說這在大部分情況下是夠用的倦西,但遇到極大復(fù)雜度的應(yīng)用,遇到性能瓶頸的時(shí)候赁严,這個(gè)流程優(yōu)化起來也是相當(dāng)復(fù)雜的一個(gè)話題扰柠。
如下圖所示,在Vue里面由于依賴追蹤系統(tǒng)的存在疼约,當(dāng)任意數(shù)據(jù)變動(dòng)的時(shí)卤档,Vue的每一個(gè)組件都精確地知道自己是否需要重繪,所以并不需要手動(dòng)優(yōu)化忆谓。用Vue渲染這些組件的時(shí)候裆装,數(shù)據(jù)變了,對(duì)應(yīng)的組件基本上去除了手動(dòng)優(yōu)化的必要性倡缠。
2.png
(2)組件系統(tǒng)
相信基本上所有的現(xiàn)代框架都已經(jīng)走向了組件化道路哨免,Web Components 從規(guī)范層面做這個(gè)實(shí)踐。主流框架都有各有不同的封裝昙沦,但核心思想都是一樣琢唾,把UI結(jié)構(gòu)映射到恰當(dāng)?shù)慕M件樹。
在Vue中盾饮,父子組件之間的通信是通過 props 傳遞采桃。從父向子單向傳遞懒熙;而如果子組件想要在父組件作用里面產(chǎn)生副作用,就需要去派發(fā)事件普办。這樣就形成一個(gè)基本的父子通信模式工扎,在涉及大規(guī)模狀態(tài)管理的時(shí)候會(huì)有額外的方案,這個(gè)后面會(huì)提到
Vue的組件引入構(gòu)建工具之后有一個(gè)單文件組件概念衔蹲,如下圖所示肢娘,就是這個(gè)Vue文件。在同一個(gè)Vue文件里舆驶,可以同時(shí)寫 template橱健、script 和 style,三個(gè)東西放在一個(gè)里面沙廉。
同時(shí)拘荡,Vue的單文件組件和 Web Components 有一個(gè)本質(zhì)不同,它是基于構(gòu)建工具實(shí)現(xiàn)撬陵。這樣的好處是有了一個(gè)構(gòu)建的機(jī)會(huì)珊皿,可以對(duì)這些單文件組件做更多的分析處,在每一個(gè)語言塊里可以單獨(dú)使用不同的處理器巨税,這點(diǎn)后面還會(huì)講到亮隙。
(3)客戶端路由
在做一個(gè)界面復(fù)雜度非常的高應(yīng)用時(shí),它會(huì)有很多的狀態(tài)垢夹,這樣的應(yīng)用顯然不可能在每做一次操作后都刷新一個(gè)頁面作為用戶反饋溢吻。這就要這個(gè)應(yīng)用有多個(gè)復(fù)雜的狀態(tài),同時(shí)這些狀態(tài)還要對(duì)應(yīng)到URL果元。
有一個(gè)重要的功能叫做 deep-linking促王,也就是當(dāng)用戶瀏覽到一個(gè)URL,然后把它傳給另外的人或者復(fù)制重新打開而晒,應(yīng)用需要直接渲染出這個(gè)URL對(duì)應(yīng)的狀態(tài)蝇狼。這就意味著應(yīng)用的URL和組件樹的狀態(tài)之間有一個(gè)映射關(guān)系,客戶端路由的職責(zé)就是讓這個(gè)映射關(guān)系聲明式地對(duì)應(yīng)起來倡怎。
可能同一層的路由有多個(gè)不同的出口迅耘,還有著復(fù)雜的URL匹配規(guī)則,等等监署。這些問題如果都由自己去一一實(shí)現(xiàn)颤专,那么復(fù)雜度是非常高的。而Vue基本都有對(duì)應(yīng)的解決方案(router.vuejs.org)钠乏。配合Webpack還可以實(shí)現(xiàn)基于路由的懶加載栖秕,一條路徑所對(duì)應(yīng)的組件在打包的時(shí)候,會(huì)分離成另外一塊晓避,只有當(dāng)該路由被訪問的時(shí)候簇捍,才會(huì)被加載出來只壳。這有相應(yīng)的解決方案,同時(shí)也有實(shí)例暑塑。
(4)狀態(tài)管理
說到狀態(tài)管理吼句,本質(zhì)上就是把整個(gè)應(yīng)用抽象為下圖中的循環(huán)。臉書最早提出 Flux 這個(gè)概念的時(shí)事格,也是一個(gè)很松散的概念命辖,而且官方的實(shí)現(xiàn)本身做得很難用。所以分蓖,社區(qū)就做了各種各樣的探索。圖中的這三個(gè)東西是一個(gè)單向數(shù)據(jù)流尔许,State 驅(qū)動(dòng) View 的渲染么鹤,而用戶對(duì) View 進(jìn)行操作產(chǎn)生 Action,會(huì)使State產(chǎn)生變化味廊,從而導(dǎo)致 View 重新渲染蒸甜。
一個(gè)單獨(dú)的Vue的組件,其實(shí)就已經(jīng)是這樣的結(jié)構(gòu)余佛。但是當(dāng)多個(gè)這樣的組件來配套的時(shí)候柠新,就會(huì)遇到一個(gè)問題。每個(gè)組件都有它自己的狀態(tài)辉巡,但整個(gè)應(yīng)用的狀態(tài)恨憎,跟組件之間并不一定存在一一對(duì)應(yīng)的關(guān)系。這個(gè)狀態(tài)可能是一個(gè)全局狀態(tài)郊楣。那么狀態(tài)到底放在哪里憔恳?大部分解決方案是把這個(gè)狀態(tài)從組件樹中提取出來,放在一個(gè)全局的 Store 里面净蚤。Vuex 也是這樣做的钥组,但是它是針對(duì) Vue 做了特化
我們看到最左邊就是Vue的組件,這些組件在大部分情況下今瀑,就不再有私有的狀態(tài)程梦,而是從全局的 Store 里面獲取狀態(tài)。Actions 和 Mutations 比較難用一兩句話說清楚橘荠,大致就是當(dāng)應(yīng)用狀態(tài)進(jìn)行改變的時(shí)候屿附,需要通過 Mutations 去顯式地觸發(fā),而 Actions 則是負(fù)責(zé)異步和其他副作用哥童。
由于 Mutations 會(huì)被記錄下來拿撩,我們可以把這些記錄發(fā)到工具里面去做分析,甚至進(jìn)行回滾如蚜。當(dāng)發(fā)現(xiàn)bug的時(shí)候压恒,這使得我們可以更好地理解大型應(yīng)用中的狀態(tài)變化影暴。更多的細(xì)節(jié),還請(qǐng)看官方文檔(vuex.vuejs.org)探赫。
(5)構(gòu)建工具
構(gòu)建工具方面型宙,Vue有一個(gè)官方的,全局安裝的 vue-cli伦吠。這里有一個(gè)筆誤妆兑。全局安裝之后,我們就可以用 vue 命令創(chuàng)建一個(gè)新的項(xiàng)目毛仪,Vue 的 CLI 跟其他 CLI 不同之處在于搁嗓,有多個(gè)可選模板,有簡(jiǎn)單的也有復(fù)雜的箱靴。極簡(jiǎn)的配置腺逛,更快的安裝,可以更快的上手衡怀。
它也有一個(gè)更完整的模板棍矛,包括單元測(cè)試在內(nèi)的各種內(nèi)容都涵蓋,但是抛杨,它的復(fù)雜度也更高够委,這又涉及到根據(jù)用例來選擇恰當(dāng)復(fù)雜度的問題。所有的模板在創(chuàng)建之后怖现,構(gòu)建腳本都是通過 npm 腳本來執(zhí)行茁帽,在國(guó)內(nèi)安裝 npm 依賴的時(shí)候有點(diǎn)卡,可以用 yarn 或者推薦用淘寶的 npm 鏡象源屈嗤,可以很大地提升安裝速度脐雪。
之前提到了單文件組件,如下圖所示恢共,支持任意的處理器战秋,開箱即用的熱重載,所以組件都支持熱重載 (hot-reload)讨韭。當(dāng)你做了修改脂信,不會(huì)刷新頁面,只是對(duì)組件本身進(jìn)行立刻重載透硝,不會(huì)影響整個(gè)應(yīng)用當(dāng)前的狀態(tài)狰闪。CSS也支持熱重載。
我們看下左下角濒生,在使用這個(gè)預(yù)處理器的同時(shí)埋泵,我們只需要添加一個(gè) scoped 特性,Vue 會(huì)通過對(duì)模板和CSS代碼的解析改寫,來模擬CSS的效果丽声。同時(shí)單文件組件也支持懶加載礁蔗,一個(gè)懶加載的組件和它的依賴會(huì)被打包成一個(gè)額外的包,只有被用到的時(shí)候才加載雁社,這對(duì)首屏的加載速度也是很有幫助的浴井。
如下圖所示,這個(gè)開發(fā)者工具本身也是用Vue寫的
2.png
使用它的話可以看到我們當(dāng)前應(yīng)用的組件樹結(jié)構(gòu)霉撵。
3.png
點(diǎn)擊組件磺浙,就可以觀察這個(gè)組件當(dāng)前的狀態(tài)。也可以把這個(gè)組件發(fā)送到控制臺(tái)里徒坡。同時(shí)這個(gè)開發(fā)者工具還有一個(gè) Vuex 面板撕氧,如果你用了 Vuex,那么每次操作都會(huì)被記錄下來喇完,記錄下來的狀態(tài)之間可以進(jìn)行跳轉(zhuǎn)伦泥。
除此之外,還支持把當(dāng)前應(yīng)用的狀態(tài)快照發(fā)送給另外一個(gè)人何暮,這個(gè)人可以在他的控制臺(tái)里導(dǎo)入你發(fā)送的狀態(tài),就可以立刻跳轉(zhuǎn)到你之前所在的狀態(tài)铐殃。這對(duì)于重現(xiàn)一些 bug海洼,或要描述當(dāng)前狀態(tài)都很有幫助。
Vue2.0
Vue2.0在不久之前剛剛發(fā)布(具體報(bào)道參見http://t.cn/RVC0foZ)富腊,之前一些技術(shù)細(xì)節(jié)在前文中已有所涉及坏逢。Vue2.0相對(duì)于1.0的改進(jìn)有以下幾點(diǎn)。
1赘被、更輕
對(duì)Vue1.0大小壓縮是整,Vue2.0它有一個(gè)只包含運(yùn)行時(shí)的版本,所有的模板在編譯的時(shí)候已經(jīng)完成了民假「∪耄基于這個(gè)版本,下圖中Vue羊异、vue-router和vuex三個(gè)(都是 2.0 版本)加一起事秀,跟Vue1.0的核心庫大小一樣。
2野舶、更快
Vue2.0可以說是當(dāng)前最快的框架之一易迹。這個(gè)是基于第三方獨(dú)立測(cè)試的結(jié)果。有興趣的話平道,可以移步鏈接進(jìn)行查看睹欲。
這個(gè)測(cè)試是一個(gè)比較綜合的測(cè)試,它對(duì)于各種操作,以及在大列表里面更新移除等窘疮,都有相當(dāng)完整的覆蓋袋哼。可以看出考余,Vue2.0先嬉,不僅僅是在Vue1.0的基礎(chǔ)上有很大提升,相比其他框架楚堤,也有相當(dāng)明顯的性能優(yōu)勢(shì)疫蔓。
3、Vue2.0 架構(gòu)
下圖是Vue2.0的架構(gòu)圖身冬,這里不深入講整個(gè)架構(gòu)的實(shí)現(xiàn)衅胀。
Vue2.0同時(shí)支持服務(wù)端,服務(wù)端渲染支持流式渲染酥筝。因?yàn)镠TTP請(qǐng)求也是流式滚躯,Vue 的服務(wù)端渲染結(jié)果可以直接 pipe 到返回的請(qǐng)求里面。這樣一來嘿歌,就可以更早地在瀏覽器中呈現(xiàn)給用戶內(nèi)容掸掏,通過合理的緩存策略,可以有效地提升服務(wù)端渲染的性能宙帝。
除了服務(wù)端渲染還有原生渲染丧凤,這里的原生渲染是指阿里巴巴的項(xiàng)目Weex。在架構(gòu)層面步脓,通過編譯一個(gè) Weex 源文件(類似于 Vue 單文件組建的格式)然后運(yùn)行愿待。界面節(jié)點(diǎn)的操作都是抽象的,這些抽象操作會(huì)派發(fā)到不同的目標(biāo)引擎做實(shí)際的渲染靴患,同時(shí)支持 iOS, Android 和 Web仍侥。
Vue和Weex現(xiàn)在有一個(gè)合作,Vue 2.0 將會(huì)正式成為 Weex 的 JavaScript 運(yùn)行時(shí)鸳君。這樣的合作可以使得符合功能交集的Vue組件可以跨平臺(tái)使用农渊。
(轉(zhuǎn)載)