構建單頁Web應用

来源:http://www.cnblogs.com/ppforever/archive/2016/01/21/5126640.html
-Advertisement-
Play Games

摘自前端農民工的博客讓我們先來看幾個網站:codingteambitioncloud9註意這幾個網站的相同點,那就是在瀏覽器中,做了原先“應當”在客戶端做的事情。它們的界面切換非常流暢,響應很迅速,跟傳統的網頁明顯不一樣,它們是什麼呢?這就是單頁Web應用。所謂單頁應用,指的是在一個頁面上集成多種功...


摘自前端農民工的博客

讓我們先來看幾個網站:

coding

teambition

cloud9

註意這幾個網站的相同點,那就是在瀏覽器中,做了原先“應當”在客戶端做的事情。它們的界面切換非常流暢,響應很迅速,跟傳統的網頁明顯不一樣,它們是什麼呢?這就是單頁Web應用。

所謂單頁應用,指的是在一個頁面上集成多種功能,甚至整個系統就只有一個頁面,所有的業務功能都是它的子模塊,通過特定的方式掛接到主界面上。它是AJAX技術的進一步升華,把AJAX的無刷新機制發揮到極致,因此能造就與桌面程式媲美的流暢用戶體驗。

其實單頁應用我們並不陌生,很多人寫過ExtJS的項目,用它實現的系統,很天然的就已經是單頁的了,也有人用jQuery或者其他框架實現過類似的東西。用各種JS框架,甚至不用框架,都是可以實現單頁應用的,它只是一種理念。有些框架適用於開發這種系統,如果使用它們,可以得到很多便利。

開發框架

ExtJS可以稱為第一代單頁應用框架的典型,它封裝了各種UI組件,用戶主要使用JavaScript來完成整個前端部分,甚至包括佈局。隨著功能逐漸增加,ExtJS的體積也逐漸增大,即使用於內部系統的開發,有時候也顯得笨重了,更不用說開發以上這類運行在互聯網上的系統。

jQuery由於偏重DOM操作,它的插件體系又比較鬆散,所以比ExtJS這個體系更適合開發在公網運行的單頁系統,整個解決方案會相對比較輕量、靈活。

但由於jQuery主要面向上層操作,它對代碼的組織是缺乏約束的。如何在代碼急劇膨脹的情況下控制每個模塊的內聚性,並且適當在模塊之間產生數據傳遞與共用,就成為了一種有挑戰的事情。

為瞭解決單頁應用規模增大時候的代碼邏輯問題,出現了不少MV*框架,他們的基本思路都是在JS層創建模塊分層和通信機制。有的是MVC,有的是MVP,有的是MVVM,而且,它們幾乎都在這些模式上產生了變異,以適應前端開發的特點。

這類框架包括Backbone,Knockout,AngularJS,Avalon等。

組件化

這些在前端做分層的框架推動了代碼的組件化,所謂組件化,在傳統的Web產品中,更多的指UI組件,但其實組件是一個廣泛概念,傳統Web產品中UI組件占比高的原因是它的厚度不足,隨著客戶端代碼比例的增加,相當一部分的業務邏輯也前端化,由此催生了很多非界面型組件的出現。

分層帶來的一個優勢是,每層的職責更專一了,由此,可以對其作單元測試的覆蓋,以保證其質量。傳統UI層測試最頭疼的問題是UI層和邏輯混雜在一起,比如往往會在遠程請求的回調中更改DOM,當引入分層之後,這些東西都可以分別被測試,然後再通過場景測試來保證整體流程。

代碼隔離

與開發傳統頁面型網站相比,實現單頁應用的過程中,有一些比較值得特別關註的點。

從單頁應用的特點來看,它比頁面型網站更加依賴於JavaScript,而由於頁面的單頁化,各種子功能的JavaScript代碼聚集到了同一個作用域,所以代碼的隔離、模塊化變得很重要。

在單頁應用中,頁面模板的使用是很普遍的。很多框架內置了特定的模板,也有的框架需要引入第三方的模板。這種模板是界面片段,我們可以把它們類比成JavaScript模塊,它們是另一種類型的組件。

模板也一樣有隔離的需要。不隔離模板,會造成什麼問題呢?模板間的衝突主要存在於id屬性上,如果一個模板中包含固定的id,當它被批量渲染的時候,會造成同一個頁面的作用域中出現多個相同id的元素,產生不可預測的後果。因此,我們需要在模板中避免使用id,如果有對DOM的訪問需求,應當通過其他選擇器來完成。如果一個單頁應用的組件化程度非常高,很可能整個應用中都沒有元素id的使用。

代碼合併與載入策略

人們對於單頁系統的載入時間容忍度與Web頁面不同,如果說他們願意為購物頁面的載入等待3秒,有可能會願意為單頁應用的首次載入等待5-10秒,但在此之後,各種功能的使用應當都比較流暢,所有子功能頁面儘量要在1-2秒時間內切換成功,否則他們就會感覺這個系統很慢。

從這些特點來看,我們可以把更多的公共功能放到首次載入,以減小每次載入的載入量,有一些站點甚至把所有的界面和邏輯全部放到首頁載入,每次業務界面切換的時候,只產生數據請求,因此它的響應是非常迅速的,比如青雲的控制台就是這麼做的。

通常在單頁應用中,無需像網站型產品一樣,為了防止文件載入阻塞渲染,把js放到html後面載入,因為它的界面基本都是動態生成的。

當切換功能的時候,除了產生數據請求,還需要渲染界面,這個新渲染的界面部件一般是界面模板,它從哪裡來呢?來源無非是兩種,一種是即時請求,像請求數據那樣通過AJAX獲取過來,另一種是內置於主界面的某些位置,比如script標簽或者不可見的textarea中,後者在切換功能的時候速度有優勢,但是加重了主頁面的負擔。

在傳統的頁面型網站中,頁面之間是互相隔離的,因此,如果在頁面間存在可復用的代碼,一般是提取成單獨的文件,並且可能會需要按照每個頁面的需求去進行合併。單頁應用中,如果總的代碼量不大,可以整體打包一次在首頁載入,如果大到一定規模,再作運行時載入,載入的粒度可以搞得比較大,不同的塊之間沒有重覆部分。

路由與狀態的管理

我們最開始看到的幾個線上應用,有的是對路由作了管理的,有的沒有。

管理路由的目的是什麼呢?是為了能減少用戶的導航成本。比如說我們有一個功能,經歷過多次導航菜單的點擊,才呈現出來。如果用戶想要把這個功能地址分享給別人,他怎麼才能做到呢?

傳統的頁面型產品是不存在這個問題的,因為它就是以頁面為單位的,也有的時候,服務端路由處理了這一切。但是在單頁應用中,這成為了問題,因為我們只有一個頁面,界面上的各種功能區塊是動態生成的。所以我們要通過對路由的管理,來實現這樣的功能。

具體的做法就是把產品功能劃分為若幹狀態,每個狀態映射到相應的路由,然後通過pushState這樣的機制,動態解析路由,使之與功能界面匹配。

有了路由之後,我們的單頁面產品就可以前進後退,就像是在不同頁面之間一樣。

其實在Web產品之外,早就有了管理路由的技術方案,Adobe Flex中,就會把比如TabNavigator,甚至下拉框的選中狀態對應到url上,因為它也是單“頁面”的產品模式,需要面對同樣的問題。

當產品狀態複雜到一定程度的時候,路由又變得很難應用了,因為狀態的管理極其麻煩,比如開始的時候我們演示的c9.io線上IDE,它就沒法把狀態對應到url上。

緩存與本地存儲

在單頁應用的運作機制中,緩存是一個很重要的環節。

由於這類系統的前端部分幾乎全是靜態文件,所以它能夠有機會利用瀏覽器的緩存機制,而比如動態載入的界面模板,也完全可以做一些自定義的緩存機制,在非首次的請求中直接取緩存的版本,以加快載入速度。

甚至,也出現了一些方案,在動態載入JavaScript代碼的同時,把它們也緩存起來。比如Addy Osmani的這個basket.js,就利用了HTML5 localStorage作了js和css文件的緩存。

在單頁產品中,業務代碼也常常會需要跟本地存儲打交道,存儲一些臨時數據,可以使用localStorage或者localStorageDB來簡化自己的業務代碼。

服務端通信

傳統的Web產品通常使用JSONP或者AJAX這樣的方式與服務端通信,但在單頁Web應用中,有很大一部分採用WebSocket這樣的實時通訊方式。

WebSocket與傳統基於HTTP的通信機制相比,有很大的優勢。它可以讓服務端很便利地使用反向推送,前端只響應確實產生業務數據的事件,減少一遍又一遍無意義的AJAX輪詢。

由於WebSocket只在比較先進的瀏覽器上被支持,有一些庫提供了在不同瀏覽器中的相容方案,比如socket.io,它在不支持WebSocket的瀏覽器上會降級成使用AJAX或JSONP等方式,對業務代碼完全透明、相容。

記憶體管理

傳統的Web頁面一般是不需要考慮記憶體的管理的,因為用戶的停留時間相對少,即使出現記憶體泄漏,可能很快就被刷新頁面之類的操作衝掉了,但單頁應用是不同的,它的用戶很可能會把它開一整天,因此,我們需要對其中的DOM操作、網路連接等部分格外小心。

樣式的規劃

在單頁應用中,因為頁面的集成度高,所有頁面聚集到同一作用域,樣式的規劃也變得重要了。

樣式規劃主要是幾個方面:

基準樣式的分離

這裡面主要包括瀏覽器樣式的重設、全局字體的設置、佈局的基本約定和響應式支持。

組件樣式的劃分

這裡面是兩個層面的規劃,首先是各種界面組件及其子元素的樣式,其次是一些修飾樣式。組件樣式應當儘量減少互相依賴,各組件的樣式允許冗餘。

堆疊次序的管理

傳統Web頁面的特點是元素多,但是層次少,單頁應用會有些不同。

在單頁應用中,需要提前為各種UI組件規劃堆疊次序,也就是z-index,比如說,我們可能會有各種彈出對話框,浮動層,它們可能組合成各種堆疊狀態。新的對話框的z-index需要比舊的高,才能確保蓋在它上面。諸如此類,都需要我們對這些可能的遮蓋作規劃,那麼,怎樣去規劃呢?

瞭解通信知識的人,應當會知道,不同的頻率段被劃分給不同的通信方式使用,在一些國家,領空的使用也是有劃分的,我們也可以用同樣的方式來預先分段,不同類型的組件的z-index落到各自的區間,以避免它們的衝突。

單頁應用的產品形態

我們在開始的時候提到,存在著很多新型Web產品,使用單頁應用的方式構建,但實際上,這類產品不僅僅存在於Web上。點開Chrome商店,我們會發現很多離線應用,這些產品都可以算是單頁應用的體現。

除了各種瀏覽器插件,藉助node-webkit這樣的外殼平臺,我們可以使用Web技術來構建本地應用,產品的主要部分仍然是我們熟悉的單頁應用。

單頁應用的流行程度正在逐漸增加,大家如果關註了一些初創型互聯網企業,會發現其中很大一部分的產品模式是單頁化的。這種模式能帶給用戶流暢的體驗,在開發階段,對JavaScript技能水平要求較高。

單頁應用開發過程中,前後端是天然分離的,雙方以API為分界。前端作為服務的消費者,後端作為服務的提供者。在此模式下,前端將會推動後端的服務化。當後端不再承擔模板渲染、輸出頁面這樣工作的情況下,它可以更專註於所提供的API的實現,而在這樣的情況下,Web前端與各種移動終端的地位對等,也逐漸使得後端API不必再為每個端作差異化設計了。

部署模式的改變

在現在這個時代,我們已經可以看到一種產品的出現了,那就是“無後端”的Web應用。這是一種什麼東西呢?基於這種理念,你的產品很可能只需要自己編寫靜態Web頁面,在某種BaaS(Backend as a Service)雲平臺上定製服務端API和雲存儲,集成這個平臺提供的SDK,通過AJAX等方式與之打交道,實現註冊認證、社交、消息推送、實時通信、雲存儲等功能。

我們觀察一下這種模式,會發現前後端的部署已經完全分離了,前端代碼完全靜態化,這意味著可以把它們放置到CDN上,訪問將大大地加速,而服務端托管在BaaS雲上,開發者也不必去關註一些部署方面的繁瑣細節。

假設你是一名創業者,正在做的是一種實時協同的單頁產品,可以在雲平臺上,快速定製後端服務,把絕大部分寶貴的時間花在開發產品本身上。

單頁應用的缺陷

單頁應用最根本的缺陷就是不利於SEO,因為界面的絕大部分都是動態生成的,所以搜索引擎很不容易索引它。

產品單頁化帶來的挑戰

一個產品想要單頁化,首先是它必須適合單頁的形態。其次,在這個過程中,對開發模式會產生一些變更,對開發技能也會有一些要求。

開發者的JavaScript技能必須過關,同時需要對組件化、設計模式有所認識,他所面對的不再是一個簡單的頁面,而是一個運行在瀏覽器環境中的桌面軟體。

 

用JS渲染的單頁面應用其實性能還是比較差的

證明這個結論之前,要先闡述一下瀏覽器的渲染機制,這裡先祭出這篇文章:《關鍵呈現路徑》,文章主要介紹了瀏覽器渲染過程,其實大家也大概都瞭解過:

image

上圖,瀏覽器通過網路請求載入頁面資源,在頁面呈現之前無論如何都要經歷以下過程:

  1. HTML→DOM
  2. CSS→CSSOM
  3. DOM + CSSOM → Render Tree
  4. 對Render Tree進行佈局計算(Layout)
  5. 對佈局結果進行屏幕繪製(Paint)

如果在JS渲染頁面模式下,需要在前端用JS載入樣式並組裝數據生成HTML插入頁面,以上瀏覽器渲染過程必須等到頁面載入完CSS,並且JS載入完數據拼裝好HTML之後才能開始進行,一般的網路時序如下:

image

大概闡述一下這個流程:

  1. 瀏覽器發起請求載入主文檔
  2. 服務端響應一個基本骨架的主文檔
  3. 瀏覽器載入主文檔中外鏈的loader.js(根據路由控制資源載入的)
  4. 服務端響應loader.js
  5. loader.js執行,根據頁面url判斷用戶訪問到哪個虛擬頁面,然後再發起請求載入對應頁面的js和css
  6. 頁面所需JS和CSS都載入完畢,JS執行,發起請求載入數據
  7. 數據載入完畢,JS執行前端模板拼裝,插入DOM節點,然後瀏覽器開始前述渲染過程
  8. 最終頁面呈現

概括一下,載入時序大概是這樣的:

 

image

以上載入過程均為串列,需要至少多付出3次RTT。如果把這種架構應用在高延遲的網路環境下(比如移動2G),那就是找死啊(其實國內現在的網路環境很好了,這樣搞問題或許不太明顯)。

當然,上面的例子還是常規了一些,有些請求可以適當合併,進一步優化之後,大概可以搞成這個樣子:

image

就是首次請求的主文檔儘量多內嵌一些東西,除了HTML骨架之外,把loader.js內嵌,再加一個loading界面,讓用戶覺得沒那麼長時間白屏,另外如果前端路由切換是pusState控制的話,可以在服務端知道前端路由url,然後在主文檔中直接內嵌數據,主文檔體積大了不少,但是可以減少2次RTT,優化對比:

image

當然,如果你的單頁面應用體量很小,完全不用按需載入,主文檔內嵌一切可以再減少一次RTT,得到:

image

不過這麼極端的做法其限制就是:你的應用千萬不能太大!

前端渲染模式我廠的代表產品:UC奇趣百科 ,其優化點:
* 主文檔loader.js內嵌、數據內嵌、loading界面內嵌
* 頁面資源按需載入,請求動態合併
* localstorage存儲JS/CSS

在國內的網路環境下感覺還OK吧。。。

兼顧性能、兼顧SEO,還是單頁面應用,是可以做到的!

很明顯,前端JS渲染由於違背了瀏覽器的優化策略,總是存在一個不可突破的瓶頸:

 

JS和數據沒載入完,JS拼裝數據的邏輯沒執行完,瀏覽器不能開始正常的渲染流程。

這個性能差異我感覺短時間內這種JS渲染的webapp是無法跟傳統頁面輸出模式相比較的,因為瀏覽器的各種渲染優化策略基本上都是圍繞著傳統頁面時序展開的。有沒有辦法突破這個性能瓶頸,並且兼顧SEO,但還保留單頁面應用的體驗呢?

答案是:有辦法。

有人或許會想到 Isomorphic Javascript,所謂的同構JavaScript,或者什麼前後端模板復用,相信我,這個概念根本就是扯淡!

其實辦法很簡單,根本用不著同構JS,頁面還是服務端拼裝好的,CSS在head中,主文檔是完整的HTML,JS在body尾部;但需要在後端模板中實現一種功能:允許通過特殊的ajax請求以json格式響應頁面中的局部區域。這項技術被稱為 Quickling

此外,單頁面應用還有一項優化手段,叫PageCache,前端控制頁面切換時,把之前的頁面緩存到記憶體中,下次再回到這個頁面就直接展現,不用再次請求數據拼裝模板渲染,進一步優化用戶在站內瀏覽的體驗。

基於Quickling和PageCache我們在印度市場(網路環境超差)實現了兩個單頁面應用產品:YoloSong 和 Huntnews ,其優化點:

    • 首次訪問服務端渲染,頁面間Quickling切換,單頁面體驗
    • 所有鏈接可爬取,解決SEO問題
    • PageCache緩存已訪問頁面,加速切換,歷史記錄前進後退
    • 可 全站禁用JS,不影響瀏覽體驗
    • 按需載入,請求合併

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 傳統的線程互斥和同步通信是通過synchronized關鍵字和wait()、notify()方法來實現的。首先介紹下synchronized關鍵字。synchronized 關鍵字,它包括兩種用法:synchronized 方法和 synchronized 塊。1. synchronized 方法:...
  • 總結:通過開發日報告提交系統,掌握了基本的phalcon框架原理和PHP語言。也瞭解了一些linux常用指令,收穫頗豐。下麵對項目中所遇到的問題進行總結:1.前臺數據傳往後臺所用的三種方法:(1)表單提交form,$this->request->getPost(‘name’);(2)超鏈接, $_G...
  • 作者:武沛齊出處:http://www.cnblogs.com/wupeiqi/本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。模塊,用一砣代碼實現了某個功能的代碼集合。類似於函數式編程和麵向過程編程,函數式編程則完成一個功能,其他代碼用來調用...
  • 前言公司同事做了一個報表系統,需要做集群部署,本來是一件挺容易的事,但是部署過程中卻遇到啦種種蛋疼問題。問題1、我們的報表使用的是微軟的水晶報表,需要上傳報表的配置文件,然後水晶報表提供的控制項來讀取文件,不支持直接圖片伺服器提供的http:www.xxxx.com/a.jpg。但是他支持\\192....
  • css3動畫border旋轉時的應用。 效果圖上面代碼中要註意的地方:border-radius: 70px;為70px時div才能顯示為圓形,因為上下左右的border多出...
  • 修改radio、checkbox、select預設樣式,支持CSS3及js兩種方式,CSS3相容IE9以上,js方式支持IE8以上。select修改預設樣式僅支持IE10以上。
  • 一: JSON 語法是 JavaScript 對象表示語法的子集,其語法規則如下:數據在鍵值對中數據由逗號分隔花括弧保存對象:{}方括弧保存數組:[] 如:[{"name":"g","age":11},{"name":"c","age":22},{"name":"s","age":33}]二:js....
  • 最近公司的一款新產品APP要進行研發,老大的意思想用H5來做混合APP以達到高效敏捷開發的目的。我自然就開始進行各種技術選型的調研,這裡重點想說的是我最後挑選出的2款hybrid app開發技術方案:RN(react native),HBuilder。React Native是大名鼎鼎的Facebo...
一周排行
    -Advertisement-
    Play Games
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...