首先我們來看一看單頁應(yīng)用是什么?所謂單頁應(yīng)用峡钓,指的是在一個頁面上集成多種功能,甚至整個系統(tǒng)就只有一個頁面,所有的業(yè)務(wù)功能都是它的子模塊杆麸,通過特定的方式掛接到主界面上。它是AJAX技術(shù)的進(jìn)一步升華浪感,把AJAX的無刷新機(jī)制發(fā)揮到極致昔头,因此能造就與桌面程序媲美的流暢用戶體驗(yàn)。
其實(shí)單頁應(yīng)用我們并不陌生影兽,很多人寫過ExtJS的項(xiàng)目揭斧,用它實(shí)現(xiàn)的系統(tǒng),很天然的就已經(jīng)是單頁的了峻堰,也有人用jQuery或者其他框架實(shí)現(xiàn)過類似的東西讹开。用各種JS框架,甚至不用框架捐名,都是可以實(shí)現(xiàn)單頁應(yīng)用的旦万,它只是一種理念。有些框架適用于開發(fā)這種系統(tǒng)镶蹋,如果使用它們成艘,可以得到很多便利。
一贺归、開發(fā)框架
ExtJS可以稱為第一代單頁應(yīng)用框架的典型淆两,它封裝了各種UI組件,用戶主要使用JavaScript來完成整個前端部分拂酣,甚至包括布局秋冰。隨著功能逐漸增加,ExtJS的體積也逐漸增大婶熬,即使用于內(nèi)部系統(tǒng)的開發(fā)剑勾,有時候也顯得笨重了光坝,更不用說開發(fā)以上這類運(yùn)行在互聯(lián)網(wǎng)上的系統(tǒng)。
jQuery由于偏重DOM操作甥材,它的插件體系又比較松散盯另,所以比ExtJS這個體系更適合開發(fā)在公網(wǎng)運(yùn)行的單頁系統(tǒng),整個解決方案會相對比較輕量洲赵、靈活鸳惯。
但由于jQuery主要面向上層操作,它對代碼的組織是缺乏約束的叠萍。如何在代碼急劇膨脹的情況下控制每個模塊的內(nèi)聚性芝发,并且適當(dāng)在模塊之間產(chǎn)生數(shù)據(jù)傳遞與共享,就成為了一種有挑戰(zhàn)的事情苛谷。
為了解決單頁應(yīng)用規(guī)模增大時候的代碼邏輯問題辅鲸,出現(xiàn)了不少M(fèi)V*框架,他們的基本思路都是在JS層創(chuàng)建模塊分層和通信機(jī)制腹殿。有的是MVC独悴,有的是MVP,有的是MVVM锣尉,而且刻炒,它們幾乎都在這些模式上產(chǎn)生了變異,以適應(yīng)前端開發(fā)的特點(diǎn)自沧。
這類框架包括Backbone坟奥,Knockout,AngularJS拇厢,Avalon等爱谁。
二、組件化
這些在前端做分層的框架推動了代碼的組件化孝偎,所謂組件化访敌,在傳統(tǒng)的Web產(chǎn)品中,更多的指UI組件邪媳,但其實(shí)組件是一個廣泛概念捐顷,傳統(tǒng)Web產(chǎn)品中UI組件占比高的原因是它的厚度不足,隨著客戶端代碼比例的增加雨效,相當(dāng)一部分的業(yè)務(wù)邏輯也前端化迅涮,由此催生了很多非界面型組件的出現(xiàn)。
分層帶來的一個優(yōu)勢是徽龟,每層的職責(zé)更專一了叮姑,由此,可以對其作單元測試的覆蓋,以保證其質(zhì)量传透。傳統(tǒng)UI層測試最頭疼的問題是UI層和邏輯混雜在一起耘沼,比如往往會在遠(yuǎn)程請求的回調(diào)中更改DOM,當(dāng)引入分層之后朱盐,這些東西都可以分別被測試群嗤,然后再通過場景測試來保證整體流程。
三兵琳、代碼隔離
與開發(fā)傳統(tǒng)頁面型網(wǎng)站相比狂秘,實(shí)現(xiàn)單頁應(yīng)用的過程中,有一些比較值得特別關(guān)注的點(diǎn)躯肌。
從單頁應(yīng)用的特點(diǎn)來看者春,它比頁面型網(wǎng)站更加依賴于JavaScript,而由于頁面的單頁化清女,各種子功能的JavaScript代碼聚集到了同一個作用域钱烟,所以代碼的隔離、模塊化變得很重要嫡丙。
在單頁應(yīng)用中拴袭,頁面模板的使用是很普遍的。很多框架內(nèi)置了特定的模板迄沫,也有的框架需要引入第三方的模板稻扬。這種模板是界面片段,我們可以把它們類比成JavaScript模塊羊瘩,它們是另一種類型的組件。
模板也一樣有隔離的需要盼砍。不隔離模板尘吗,會造成什么問題呢?模板間的沖突主要存在于id屬性上,如果一個模板中包含固定的id浇坐,當(dāng)它被批量渲染的時候睬捶,會造成同一個頁面的作用域中出現(xiàn)多個相同id的元素,產(chǎn)生不可預(yù)測的后果近刘。因此擒贸,我們需要在模板中避免使用id,如果有對DOM的訪問需求觉渴,應(yīng)當(dāng)通過其他選擇器來完成介劫。如果一個單頁應(yīng)用的組件化程度非常高,很可能整個應(yīng)用中都沒有元素id的使用案淋。
四座韵、代碼合并與加載策略
人們對于單頁系統(tǒng)的加載時間容忍度與Web頁面不同,如果說他們愿意為購物頁面的加載等待3秒,有可能會愿意為單頁應(yīng)用的首次加載等待5-10秒誉碴,但在此之后宦棺,各種功能的使用應(yīng)當(dāng)都比較流暢,所有子功能頁面盡量要在1-2秒時間內(nèi)切換成功黔帕,否則他們就會感覺這個系統(tǒng)很慢代咸。
從這些特點(diǎn)來看,我們可以把更多的公共功能放到首次加載成黄,以減小每次加載的載入量呐芥,有一些站點(diǎn)甚至把所有的界面和邏輯全部放到首頁加載,每次業(yè)務(wù)界面切換的時候慨默,只產(chǎn)生數(shù)據(jù)請求贩耐,因此它的響應(yīng)是非常迅速的,比如青云的控制臺就是這么做的厦取。
通常在單頁應(yīng)用中潮太,無需像網(wǎng)站型產(chǎn)品一樣,為了防止文件加載阻塞渲染虾攻,把js放到html后面加載铡买,因?yàn)樗慕缑婊径际莿討B(tài)生成的。
當(dāng)切換功能的時候霎箍,除了產(chǎn)生數(shù)據(jù)請求奇钞,還需要渲染界面,這個新渲染的界面部件一般是界面模板漂坏,它從哪里來呢?來源無非是兩種景埃,一種是即時請求,像請求數(shù)據(jù)那樣通過AJAX獲取過來顶别,另一種是內(nèi)置于主界面的某些位置谷徙,比如script標(biāo)簽或者不可見的textarea中,后者在切換功能的時候速度有優(yōu)勢驯绎,但是加重了主頁面的負(fù)擔(dān)完慧。
在傳統(tǒng)的頁面型網(wǎng)站中,頁面之間是互相隔離的剩失,因此屈尼,如果在頁面間存在可復(fù)用的代碼,一般是提取成單獨(dú)的文件拴孤,并且可能會需要按照每個頁面的需求去進(jìn)行合并脾歧。
單頁應(yīng)用中,如果總的代碼量不大乞巧,可以整體打包一次在首頁載入涨椒,如果大到一定規(guī)模,再作運(yùn)行時加載,加載的粒度可以搞得比較大蚕冬,不同的塊之間沒有重復(fù)部分免猾。
五、路由與狀態(tài)的管理
我們最開始看到的幾個在線應(yīng)用囤热,有的是對路由作了管理的猎提,有的沒有。
管理路由的目的是什么呢?是為了能減少用戶的導(dǎo)航成本旁蔼。比如說我們有一個功能锨苏,經(jīng)歷過多次導(dǎo)航菜單的點(diǎn)擊,才呈現(xiàn)出來棺聊。
如果用戶想要把這個功能地址分享給別人伞租,他怎么才能做到呢?
傳統(tǒng)的頁面型產(chǎn)品是不存在這個問題的,因?yàn)樗褪且皂撁鏋閱挝坏南夼澹灿械臅r候葵诈,服務(wù)端路由處理了這一切。
但是在單頁應(yīng)用中祟同,這成為了問題作喘,因?yàn)槲覀冎挥幸粋€頁面,界面上的各種功能區(qū)塊是動態(tài)生成的晕城。所以我們要通過對路由的管理泞坦,來實(shí)現(xiàn)這樣的功能。
具體的做法就是把產(chǎn)品功能劃分為若干狀態(tài)砖顷,每個狀態(tài)映射到相應(yīng)的路由贰锁,然后通過pushState這樣的機(jī)制,動態(tài)解析路由滤蝠,使之與功能界面匹配李根。
有了路由之后,我們的單頁面產(chǎn)品就可以前進(jìn)后退几睛,就像是在不同頁面之間一樣。
其實(shí)在Web產(chǎn)品之外粤攒,早就有了管理路由的技術(shù)方案所森,AdobeFlex中,就會把比如TabNavigator夯接,甚至下拉框的選中狀態(tài)對應(yīng)到url上焕济,因?yàn)樗彩菃巍绊撁妗钡漠a(chǎn)品模式,需要面對同樣的問題盔几。
當(dāng)產(chǎn)品狀態(tài)復(fù)雜到一定程度的時候晴弃,路由又變得很難應(yīng)用了,因?yàn)闋顟B(tài)的管理極其麻煩,比如開始的時候我們演示的c9.io在線IDE上鞠,它就沒法把狀態(tài)對應(yīng)到url上际邻。
六、緩存與本地存儲
在單頁應(yīng)用的運(yùn)作機(jī)制中芍阎,緩存是一個很重要的環(huán)節(jié)世曾。
由于這類系統(tǒng)的前端部分幾乎全是靜態(tài)文件,所以它能夠有機(jī)會利用瀏覽器的緩存機(jī)制谴咸,而比如動態(tài)加載的界面模板轮听,也完全可以做一些自定義的緩存機(jī)制,在非首次的請求中直接取緩存的版本岭佳,以加快加載速度血巍。
甚至,也出現(xiàn)了一些方案珊随,在動態(tài)加載JavaScript代碼的同時述寡,把它們也緩存起來。比如AddyOsmani的這個basket.js玫恳,就利用了HTML5localStorage作了js和css文件的緩存辨赐。
在單頁產(chǎn)品中,業(yè)務(wù)代碼也常常會需要跟本地存儲打交道京办,存儲一些臨時數(shù)據(jù)掀序,可以使用localStorage或者localStorageDB來簡化自己的業(yè)務(wù)代碼。
七惭婿、服務(wù)端通信
傳統(tǒng)的Web產(chǎn)品通常使用JSONP或者AJAX這樣的方式與服務(wù)端通信不恭,但在單頁Web應(yīng)用中,有很大一部分采用WebSocket這樣的實(shí)時通訊方式财饥。
WebSocket與傳統(tǒng)基于HTTP的通信機(jī)制相比换吧,有很大的優(yōu)勢。它可以讓服務(wù)端很便利地使用反向推送钥星,前端只響應(yīng)確實(shí)產(chǎn)生業(yè)務(wù)數(shù)據(jù)的事件沾瓦,減少一遍又一遍無意義的AJAX輪詢。
由于WebSocket只在比較先進(jìn)的瀏覽器上被支持谦炒,有一些庫提供了在不同瀏覽器中的兼容方案贯莺,比如socket.io,它在不支持WebSocket的瀏覽器上會降級成使用AJAX或JSONP等方式宁改,對業(yè)務(wù)代碼完全透明缕探、兼容。
八还蹲、內(nèi)存管理
傳統(tǒng)的Web頁面一般是不需要考慮內(nèi)存的管理的爹耗,因?yàn)橛脩舻耐A魰r間相對少耙考,即使出現(xiàn)內(nèi)存泄漏,可能很快就被刷新頁面之類的操作沖掉了潭兽,但單頁應(yīng)用是不同的倦始,它的用戶很可能會把它開一整天,因此讼溺,我們需要對其中的DOM操作楣号、網(wǎng)絡(luò)連接等部分格外小心。
九怒坯、樣式的規(guī)劃
在單頁應(yīng)用中炫狱,因?yàn)轫撁娴募啥雀撸许撁婢奂酵蛔饔糜蛱拊常瑯邮降囊?guī)劃也變得重要了视译。
樣式規(guī)劃主要是幾個方面:
1归敬、基準(zhǔn)樣式的分離
這里面主要包括瀏覽器樣式的重設(shè)、全局字體的設(shè)置汪茧、布局的基本約定和響應(yīng)式支持。
2舱污、組件樣式的劃分
這里面是兩個層面的規(guī)劃呀舔,首先是各種界面組件及其子元素的樣式,其次是一些修飾樣式媚赖。組件樣式應(yīng)當(dāng)盡量減少互相依賴珠插,各組件的樣式允許冗余惧磺。
3、堆疊次序的管理
傳統(tǒng)Web頁面的特點(diǎn)是元素多捻撑,但是層次少磨隘,單頁應(yīng)用會有些不同顾患。
在單頁應(yīng)用中,需要提前為各種UI組件規(guī)劃堆疊次序描验,也就是z-index坑鱼,比如說絮缅,我們可能會有各種彈出對話框呼股,浮動層,它們可能組合成各種堆疊狀態(tài)彭谁。新的對話框的z-index需要比舊的高缠局,才能確保蓋在它上面。諸如此類狭园,都需要我們對這些可能的遮蓋作規(guī)劃,那么罚舱,怎樣去規(guī)劃呢?
了解通信知識的人绎谦,應(yīng)當(dāng)會知道,不同的頻率段被劃分給不同的通信方式使用窃肠,在一些國家,領(lǐng)空的使用也是有劃分的赃蛛,我們也可以用同樣的方式來預(yù)先分段,不同類型的組件的z-index落到各自的區(qū)間呕臂,以避免它們的沖突肪跋。
十、單頁應(yīng)用的產(chǎn)品形態(tài)
我們在開始的時候提到州既,存在著很多新型Web產(chǎn)品,使用單頁應(yīng)用的方式構(gòu)建阐虚,但實(shí)際上蚌卤,這類產(chǎn)品不僅僅存在于Web上奥秆。點(diǎn)開Chrome商店咸灿,我們會發(fā)現(xiàn)很多離線應(yīng)用,這些產(chǎn)品都可以算是單頁應(yīng)用的體現(xiàn)悼瘾。
除了各種瀏覽器插件审胸,借助node-webkit這樣的外殼平臺,我們可以使用Web技術(shù)來構(gòu)建本地應(yīng)用箩绍,產(chǎn)品的主要部分仍然是我們熟悉的單頁應(yīng)用尺上。
單頁應(yīng)用的流行程度正在逐漸增加,大家如果關(guān)注了一些初創(chuàng)型互聯(lián)網(wǎng)企業(yè)怎抛,會發(fā)現(xiàn)其中很大一部分的產(chǎn)品模式是單頁化的。這種模式能帶給用戶流暢的體驗(yàn)豆赏,在開發(fā)階段富稻,對JavaScript技能水平要求較高。
單頁應(yīng)用開發(fā)過程中抚岗,前后端是天然分離的哪怔,雙方以API為分界。前端作為服務(wù)的消費(fèi)者胚委,后端作為服務(wù)的提供者叉信。
在此模式下,前端將會推動后端的服務(wù)化硼身。當(dāng)后端不再承擔(dān)模板渲染枢冤、輸出頁面這樣工作的情況下铜秆,它可以更專注于所提供的API的實(shí)現(xiàn)连茧,而在這樣的情況下巍糯,Web前端與各種移動終端的地位對等,也逐漸使得后端API不必再為每個端作差異化設(shè)計了胧瓜。
十一、部署模式的改變
在現(xiàn)在這個時代针姿,我們已經(jīng)可以看到一種產(chǎn)品的出現(xiàn)了厌衙,那就是“無后端”的Web應(yīng)用。這是一種什么東西呢?基于這種理念婶希,你的產(chǎn)品很可能只需要自己編寫靜態(tài)Web頁面喻杈,在某種BaaS(BackendasaService)云平臺上定制服務(wù)端API和云存儲,集成這個平臺提供的SDK筒饰,通過AJAX等方式與之打交道,實(shí)現(xiàn)注冊認(rèn)證盟猖、社交换棚、消息推送、實(shí)時通信娘汞、云存儲等功能夕玩。
我們觀察一下這種模式惊豺,會發(fā)現(xiàn)前后端的部署已經(jīng)完全分離了禽作,前端代碼完全靜態(tài)化,這意味著可以把它們放置到CDN上烹俗,訪問將大大地加速萍程,而服務(wù)端托管在BaaS云上,開發(fā)者也不必去關(guān)注一些部署方面的繁瑣細(xì)節(jié)蕉鸳。
假設(shè)你是一名創(chuàng)業(yè)者忍法,正在做的是一種實(shí)時協(xié)同的單頁產(chǎn)品,可以在云平臺上衍锚,快速定制后端服務(wù)嗤堰,把絕大部分寶貴的時間花在開發(fā)產(chǎn)品本身上。
十二踢匣、單頁應(yīng)用的缺陷
單頁應(yīng)用最根本的缺陷就是不利于SEO离唬,因?yàn)榻缑娴慕^大部分都是動態(tài)生成的,所以搜索引擎很不容易索引它输莺。
十三嫂用、產(chǎn)品單頁化帶來的挑戰(zhàn)
一個產(chǎn)品想要單頁化,首先是它必須適合單頁的形態(tài)嘱函。其次,在這個過程中疏唾,對開發(fā)模式會產(chǎn)生一些變更,對開發(fā)技能也會有一些要求喉童。
開發(fā)者的JavaScript技能必須過關(guān)顿天,同時需要對組件化、設(shè)計模式有所認(rèn)識,他所面對的不再是一個簡單的頁面钟沛,而是一個運(yùn)行在瀏覽器環(huán)境中的桌面軟件。
最后叁扫,給大家推薦一個前端學(xué)習(xí)進(jìn)階內(nèi)推交流群685910553(前端資料分享)莫绣,不管你在地球哪個方位悠鞍,
不管你參加工作幾年都?xì)g迎你的入駐!(群內(nèi)會定期免費(fèi)提供一些群主收藏的免費(fèi)學(xué)習(xí)書籍資料以及整理好的面試題和答案文檔Q谝恕)
如果您對這個文章有任何異議么翰,那么請?jiān)谖恼略u論處寫上你的評論。
如果您覺得這個文章有意思浩嫌,那么請分享并轉(zhuǎn)發(fā)码耐,或者也可以關(guān)注一下表示您對我們文章的認(rèn)可與鼓勵。
愿大家都能在編程這條路伐坏,越走越遠(yuǎn)。