Web應用開發的七項原則

Lenciel發表於2014-12-12

這篇文章主要介紹構建使用Javascript來控制UI的網站在設計時的7個原則。它們是我作為一名開發人員的經驗所得,也是我作為一名網際網路資深使用者的體會和總結。

Javascript毫無疑問早已成為了前端開發人員不可或缺的工具。但現在它的使用範圍還在不斷擴充套件到其他的領域,比如伺服器端甚至是微控制器。在史丹佛這樣的聲望卓越的大學裡面,它也已經被選為電腦科學入門課程的教學語言。

即便如此,它在web開發中究竟應該扮演什麼樣的角色或者說負責哪方面的作用,仍然是個迷:即便對於很多框架和類庫的作者而言也是如此:

  • JavaScript應該被用來替代像historynavigationpage rendering 這樣的瀏覽器函式麼?
  • 伺服器端開發是不是到頭了?是不是根本就不該在伺服器端渲染HTML了?
  • Single Page Applications (SPAs) 是不是代表著未來的趨勢?
  • 一個網站和一個Web應用之間的區別精確的描述起來究竟是什麼? 是不是應該就是一個東西?
  • 在網站上,JS應該用來 增強 頁面的效果,而在Web應用中,則被用來 渲染 整個頁面?
  • 是否應該使用像PJAX或者TurboLinks這樣的技術?

下面就是我試著回答這些問題做的一些分析。我的分析是通過使用者體驗(UX)層面,特別是如何最小化使用者拿到他們感興趣的 資料 的時間,作為切入點,來驗證對Javascript的 各種 使用方式。我會從網路通訊的基礎入手,一直說到對未來趨勢的預測。

  1. Server渲染頁面仍然是必須的
  2. 對使用者輸入立刻響應
  3. 資料變更時的應對
  4. 控制與伺服器的資料互動
  5. 不要破壞history,增強它
  6. 推送程式碼更新
  7. 行為預測

1. Server渲染頁面仍然是必須的

TL;DR伺服器端渲染與SEO無關,它主要的考慮是效能:需要考慮的包括不在伺服器渲染的話,請求指令碼、頁面樣式、頁面資源和API請求造成的額外的開銷,以及考慮在HTTP2.0里加入的PUSH of resources.

首先需要指出,在業界有一種錯誤的二分法:”server-rendered apps” 和 “single-page apps”的對立。如果我們的目標是使用者體驗和效能的最優化,那麼選擇其中任何一個而拋棄另一個都是錯誤的決定。原因其實很明顯:整個網際網路用於傳輸頁面的介質,有一個理論上可計算的速度侷限。關於這點,Stuart Cheshire有個著名的文獻 (或者說是吐槽?),“It’s the latency, stupid” :

The distance from Stanford to Boston is 4320km.
The speed of light in vacuum is 300 x 10^6 m/s.
The speed of light in fibre is roughly 66% of the speed of light in vacuum.
The speed of light in fibre is 300 x 10^6 m/s * 0.66 = 200 x 10^6 m/s.
The one-way delay to Boston is 4320 km / 200 x 10^6 m/s = 21.6ms.
The round-trip time to Boston and back is 43.2ms.
The current ping time from Stanford to Boston over today’s Internet is about 85ms (…)
So: the hardware of the Internet can currently achieve within a factor of two of the speed of light.

這裡提到的從波士頓到史丹佛路上花費的85ms,當然會隨著時間的推移不斷的改善:如果你現在測試一下說不定已經大大增速了。但需要注意很重要的一點:就算達到了光速,這兩個海岸間最少也需要 50ms 才能完成通訊。

換句話說,使用者間連線的頻寬再怎麼顯著提高,花在傳輸路上的延遲總有無法突破的速度極限。所以,在頁面上顯示資訊時減少請求次數,也就是減少資訊被傳輸在路上的次數,對於良好的使用者體驗和出色的響應速度而言,至關重要。

這一點在Javascript驅動的Web應用流行起來之後顯得尤為明顯。這些應用一般<body>標籤內什麼東西都沒有,只有<script><link>標籤,被稱為”Single Page Applications”或者”SPA”。就像它的名字所暗示的一樣,伺服器返回時一直在重用同一個頁面,其他的頁面內容都是在客戶端被處理和渲染的。

考慮下面的這個場景:使用者在瀏覽器上訪問http://app.com/orders/,如果這是一個傳統的網頁,那麼在後臺處理這個請求的時,就會帶回重要的 資訊 ,用來完成頁面的顯示:比如,從資料庫裡面查詢出訂單,然後把它們的資料放在請求的返回裡面。但如果這是一個SPA,那麼第一次可能會立刻返回一個包含<script>標籤的空頁面,然後再跑一趟才能拿回用來渲染頁面的內容和資料。

SPA code breakdown圖1. 伺服器端傳送的SPA的每個頁面組成結構分析

目前大多數的開發者都大方接受了這個額外的 網路傳輸過程 是因為他們確信這隻發生一次:後面反正是有cache的。也就是說,大家形成了這麼一個共識,既然整個程式碼包一旦載入一次,就可以不用再請求其他的指令碼和資源就完成對絕大多數的使用者互動(包括跳轉到應用的其他頁面)的處理,那麼這個開銷就是可以接受的。

但實際上,雖然有cache,指令碼解析和執行的時間仍然會帶來效能上的下降。“Is jQuery Too Big For Mobile?” 這篇文章就探討了即便是載入一個jQuery庫,就會花去一些瀏覽器數百毫秒的時間。

更糟糕的是,和以前網速慢那種圖片慢慢載入的效果不同,如果是指令碼正在載入,使用者什麼都看不到:在整個頁面被渲染出來之前,只能顯示空白的頁面。

最重要的是,目前網際網路資料傳輸主要的協議TCP 建立 比較慢。

首先,我們知道,一個TCP連線先需要握手。如果處於安全考慮使用了SSL,就還需要額外的兩個來回(客戶端重用了session的話,也需要一個額外的來回)。這些流程完畢之後,伺服器才能開始往客戶端傳送資料。換句話說,再小的程式碼包實際上也需要幾個來回才能完成傳輸,這就讓前面描述的問題變得更加糟糕。

其次,TCP協議裡面有一個流控機制,被稱為 slow start,也就是在連線建立過程中逐漸增加傳輸的分段(segments)大小,入下圖所示:

TCP segments chart圖2. 伺服器端在TCP連線的不同階段能夠傳送的分段大小(KB)

這對SPA有兩個很大的影響:

  1. 檔案比較大的指令碼,花在下載上的時間比你想象中的要長得多。Google的Ilya Grigorik在他的專著“High Performance Browser Networking” 裡面說過,“4個來回(…)和數百毫秒的延遲都花在從伺服器下載64KB的檔案到客戶端上了”,從前面的圖也可以看到,基本是比較高速的網路連線,比如倫敦和紐約之間,一個TCP連線要達到最大速度,也需要花上大概225ms。
  2. 因為前面說的延遲對首個頁面訪問也是有效的,所以你讓什麼資料最先被傳輸就顯得非常重要了。Paul Irish在他的演講“Delivering the Goods”給出的結論是,一個Web應用最開始的 14kb 資料是最重要的。

在足夠短的時間窗內完成內容傳輸(哪怕只是呈現基本的沒有資料的layout)的網站,就是響應良好的。這也是為什麼對於很多習慣了在伺服器端處理資料的軟體開發者覺得Javascript很多時候根本沒必要用,或者是在很有限的情況下用用就行了。當這些開發者使用的是配置良好的伺服器和資料庫,又有CDN來做部署和分發時,他們這種感覺會非常明顯。

但是,伺服器在輔助和加速頁面內容的分發和渲染中應該被怎麼使用,也是需要根據每個應用場景仔細分析的,絕對不是“把整個頁面交給伺服器渲染吧”那麼簡單的事情。在一些情況下,如果頁面上的內容對使用者並不是非看不可的,就可以不放在第一個響應中返回,而是讓客戶端在後面的操作中到伺服器去取。

比如,有的應用會先把一個”殼”頁面返回給客戶端,然後在這個頁面上併發的請求多個部分的資料。這樣即使在後臺連線速度較慢的情況下,仍然能夠有較好的響應速度。還有的應用會把 “瀏覽器裡面的第一個整屏” 顯示的頁面做預渲染。

伺服器能夠根據當前處理的session,使用者和URL對指令碼和樣式檔案進行分類也是很重要的。舉例來說,用來對訂單進行分類的指令碼,對於/orders這個URL顯然是重要的,而處理”首選項”的邏輯的指令碼就不那麼重要。再比如說,我們可以對CSS樣式表進行分類,比如區分“結構性的樣式”和“皮膚和模板的樣式”等。前面這類很可能對Javascript的正確執行是必須的,因此需要 阻塞 的方式載入, 後面這類則可以用非同步的方式載入。

到目前為止,在伺服器端處理一部分或者所有的頁面,仍然是避免過多客戶端與伺服器的互動的主要手段。StackOverflow in 4096 bytes很不錯地展示瞭如何降低和伺服器的來回互動次數。作為概念驗證的SPA,它理論上可以做到在握手後的第一個TCP連線中完成載入!當然,要做到這些,它使用了SPDY 或者 HTTP/2 server push,因此可以在一個hop裡面傳輸所有客戶端可以快取的程式碼。

StackOverflow clone in 4096 bytes

圖3. 使用了內鏈CSS和JS技術的Stackoverflow in 4096 bytes

如果我們有一個足夠靈活的系統,可以在瀏覽器和伺服器直接共享渲染頁面的程式碼(比如雙方都是js),並且提供工具增量的載入指令碼和樣式,那麼 網站 和 Web應用 就可以合一而不再是兩個模稜兩可難以區分的詞了:它們本身就有一樣的UX要素。比如一個部落格頁面和一個複雜的CRM,都有URL,都需要跳轉,都展示資料,本質上並沒有太大不同。即便是像資料表格這樣複雜的東西,傳統上主要是客戶端提供的功能來完成對資料的處理,但也首先需要給使用者展示那些需要他處理的資料 。降低客戶端和伺服器互動的次數,對實現我們說的這樣的系統非常重要。

在我看來,我們看到的大量系統上採用了這樣那樣效能上的權宜之策,是因為整個技術棧的複雜度在不斷累加。Javascript和CSS這樣的技術是被逐漸加入到系統的,它們的風靡又花了一段時間。儘管有人希望在協議上做出改進,來增強效能(比如SPDY或者QUIC),但應用層顯然才是最需要改進的地方。

要理解速度的重要性,去重溫一下WWW和HTML創立之初的一些討論是非常有用的。特別是在1997年提議在HTML里加入img這個標籤的時候,Marc Andreessen在下面這個郵件thread裡反覆強調了提供資訊的速度有多麼重要:

“If a document has to be pieced together on the fly, it could get arbitrarily complex, and even if that were limited, we’d certainly start experiencing major hits on performance for documents structured in this way. This essentially throws the **single-hop principle of WWW** out the door (well, IMG does that too, but for a very specific reason and in a very limited sense) — are we sure we want to do that?”

2. 對使用者輸入立刻響應

TL;DR我們可以使用JavaScript來掩蓋網路的延遲,把它作為設計原則,就可以在你自己的應用裡面去掉絕大多數的spinner或者loading。使用PJAX和TurboLink的話,你就會失去了這些改善使用者速度體驗的機會。.

第一個原則裡,在描述為什麼要儘量減少前端和後端之間資料來回傳輸的次數時,主要是基於傳輸速度有理論上限的事實。實際上另一個需要考慮的要素就是網路的質量。我們都知道,當網路連線狀況不好時,就會有資料包需要被重傳。所以,你覺得應該一個來回就傳輸完畢的資料,可能實際上要花去好幾個。

在這方面,Javascript正好可以幫上忙:通過客戶端的程式碼來驅動UI,人工的構造出零延遲,就可以掩蓋網路的延遲,製造一切操作都很順暢的假象。比如,網頁和網頁之間是通過超連結,<a>標籤,連結在一起的。傳統網頁上,當一個連結被點選時,瀏覽器就傳送一個可能會耗時很久的請求,然後處理請求並把內容呈現給使用者。

但Javascript允許你立刻響應(有些地方把這個叫樂觀響應):當一個連結或者按鈕被點選時,頁面立刻做出響應而不需要去訪問網路。這方面著名的例子就是Gmail(包括最近Google的新產品Inbox)的”郵件歸檔”功能。當你點選”歸檔”,UI上郵件立刻會被顯示為歸檔狀態,而伺服器的請求和處理是非同步進行的。

再比如,我們處理的是一個表單。也許你覺得一個表單在資料被提交到伺服器,處理結果返回之前,不能做太多的事情。但其實當使用者完成輸入並點選提交的時候,我們就可以開始響應了。甚至有些做到極致的應用,比如Google搜尋頁面,當使用者開始輸入的時候,展示搜尋結果的頁面就已經開始渲染了。

Google Homepage

圖4. Google在使用者輸入搜素關鍵字時就開始渲染搜尋結果頁面

這種行為被稱為 layout adaptation。 它的思路是當前頁面知道操作後狀態的頁面layout,所以在沒有資料填充的情況下,它就可以過渡到下面那個狀態的layout。這樣的處理是”樂觀”的,是因為有可能後面那個頁面的資料一直沒有返回,而這時候頁面的layout已經畫在那裡了。

Google的主頁的演進,非常清楚的說明了我們這裡強調的第一和第二個原則。

首先,分析訪問www.google.com時TCP連線的包資料可以看到整個首頁的資料都被一次性發出來了。整個互動,包括關閉連線,耗時幾十毫秒而已。而且,似乎在Google一開始的版本就做到了這點。

在2004年晚些時候, Google標杆性地使用了JavaScript完成輸入時動態提示功能(和Gmail一樣,也是一個20%創新時間產出的專案),這一功能也啟發了很多網站開始大量的使用AJAX:

Take a look at Google Suggest. Watch the way the suggested terms update as you type, almost instantly with no waiting for pages to reload. Google Suggest and Google Maps are two examples of a new approach to web applications that we at Adaptive Path have been calling Ajax

到了2010年,Google又推出了及時搜尋,也就是我們前面看到的效果:當使用者輸入關鍵字時,整個頁面無需重新整理就可以展示搜尋的結果。

另一個例子是iOS。在很早期的版本,iPhone就要求開發者提供一個default.png圖片,用來在應用被載入完成之前顯示給使用者:

iPhone default

圖5. iPhone OS強制在應用載入前顯示一個default.png

當然,這裡OS不是在隱藏網路延遲,而是CPU處理延遲。對於iPhone初期版本來說,這樣來彌補硬體的弱點非常重要。當然就和網頁上使用提前載入一樣,這種手法有可能會崩壞:當載入來的資料和default.png不匹配的時候。Marco Arment在2010年對它可能帶來的影響進行了 透徹的分析

除開處理表單和輸入,Javascript還被大量用於處理檔案上傳。我們可以通過各種前端表現來滿足使用者上傳檔案的需求:拖拽,貼上以及各種file picker。特別是有了HTML5的新API之後,我們可以在檔案完成傳輸前就顯示它的資訊。在Cloudup網站的上傳檔案中,就使用了類似的實現。從圖片中可以看到,在使用者選擇了檔案之後,縮圖就立刻生成並顯示在使用者介面上了:

Cloudup upload

圖6. 在上傳完成前圖片就被顯示出來並且加入了虛化效果

上面的方式都是採用前端技術來製造速度的假象,但這種方式其實在很多地方都被證明是有效的。一個例子是在美國休斯頓

相關文章