Javascript毫無疑問早已成為了前端開發(fā)人員不可或缺的工具。但現(xiàn)在它的使用范圍還在不斷擴(kuò)展到其他的領(lǐng)域,比如服務(wù)器端甚至是微控制器。在斯坦福這樣的聲望卓越的大學(xué)里面,它也已經(jīng)被選為計算機科學(xué)入門課程的教學(xué)語言。
即便如此,它在web開發(fā)中究竟應(yīng)該扮演什么樣的角色或者說負(fù)責(zé)哪方面的作用,仍然是個迷:即便對于很多框架和類庫的作者而言也是如此:
- JavaScript應(yīng)該被用來替代像history,navigation和page rendering 這樣的瀏覽器函數(shù)么?
- 服務(wù)器端開發(fā)是不是到頭了?是不是根本就不該在服務(wù)器端渲染HTML了?
- Single Page Applications (SPAs) 是不是代表著未來的趨勢?
- 一個網(wǎng)站和一個Web應(yīng)用之間的區(qū)別精確的描述起來究竟是什么? 是不是應(yīng)該就是一個東西?
- 在網(wǎng)站上,JS應(yīng)該用來 增強 頁面的效果,而在Web應(yīng)用中,則被用來 渲染 整個頁面?
- 是否應(yīng)該使用像PJAX或者TurboLinks這樣的技術(shù)?
下面就是我試著回答這些問題做的一些分析。我的分析是通過用戶體驗(UX)層面,特別是如何最小化用戶拿到他們感興趣的 數(shù)據(jù) 的時間,作為切入點,來驗證對Javascript的 各種 使用方式。我會從網(wǎng)絡(luò)通信的基礎(chǔ)入手,一直說到對未來趨勢的預(yù)測。
- Server渲染頁面仍然是必須的
- 對用戶輸入立刻響應(yīng)
- 數(shù)據(jù)變更時的應(yīng)對
- 控制與服務(wù)器的數(shù)據(jù)交互
- 不要破壞history,增強它
- 推送代碼更新
- 行為預(yù)測
1. Server渲染頁面仍然是必須的
TL;DR: 服務(wù)器端渲染與SEO無關(guān),它主要的考慮是性能:需要考慮的包括不在服務(wù)器渲染的話,請求腳本、頁面樣式、頁面資源和API請求造成的額外的開銷,以及考慮在HTTP2.0里加入的PUSH of resources.
首先需要指出,在業(yè)界有一種錯誤的二分法:”server-rendered apps” 和 “single-page apps”的對立。如果我們的目標(biāo)是用戶體驗和性能的最優(yōu)化,那么選擇其中任何一個而拋棄另一個都是錯誤的決定。原因其實很明顯:整個互聯(lián)網(wǎng)用于傳輸頁面的介質(zhì),有一個理論上可計算的速度局限。關(guān)于這點,Stuart Cheshire有個著名的文獻(xiàn) (或者說是吐槽?),“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,當(dāng)然會隨著時間的推移不斷的改善:如果你現(xiàn)在測試一下說不定已經(jīng)大大增速了。但需要注意很重要的一點:就算達(dá)到了光速,這兩個海岸間最少也需要 50ms 才能完成通信。
換句話說,用戶間連接的帶寬再怎么顯著提高,花在傳輸路上的延遲總有無法突破的速度極限。所以,在頁面上顯示信息時減少請求次數(shù),也就是減少信息被傳輸在路上的次數(shù),對于良好的用戶體驗和出色的響應(yīng)速度而言,至關(guān)重要。
這一點在Javascript驅(qū)動的Web應(yīng)用流行起來之后顯得尤為明顯。這些應(yīng)用一般<body>標(biāo)簽內(nèi)什么東西都沒有,只有<script>和<link>標(biāo)簽,被稱為”Single Page Applications”或者”SPA”。就像它的名字所暗示的一樣,服務(wù)器返回時一直在重用同一個頁面,其他的頁面內(nèi)容都是在客戶端被處理和渲染的。
考慮下面的這個場景:用戶在瀏覽器上訪問http://app.com/orders/,如果這是一個傳統(tǒng)的網(wǎng)頁,那么在后臺處理這個請求的時,就會帶回重要的 信息 ,用來完成頁面的顯示:比如,從數(shù)據(jù)庫里面查詢出訂單,然后把它們的數(shù)據(jù)放在請求的返回里面。但如果這是一個SPA,那么第一次可能會立刻返回一個包含<script>標(biāo)簽的空頁面,然后再跑一趟才能拿回用來渲染頁面的內(nèi)容和數(shù)據(jù)。
圖1. 服務(wù)器端發(fā)送的SPA的每個頁面組成結(jié)構(gòu)分析
目前大多數(shù)的開發(fā)者都大方接受了這個額外的 網(wǎng)絡(luò)傳輸過程 是因為他們確信這只發(fā)生一次:后面反正是有cache的。也就是說,大家形成了這么一個共識,既然整個代碼包一旦加載一次,就可以不用再請求其他的腳本和資源就完成對絕大多數(shù)的用戶交互(包括跳轉(zhuǎn)到應(yīng)用的其他頁面)的處理,那么這個開銷就是可以接受的。
但實際上,雖然有cache,腳本解析和執(zhí)行的時間仍然會帶來性能上的下降。“Is jQuery Too Big For Mobile?” 這篇文章就探討了即便是加載一個jQuery庫,就會花去一些瀏覽器數(shù)百毫秒的時間。
更糟糕的是,和以前網(wǎng)速慢那種圖片慢慢加載的效果不同,如果是腳本正在加載,用戶什么都看不到:在整個頁面被渲染出來之前,只能顯示空白的頁面。
最重要的是,目前互聯(lián)網(wǎng)數(shù)據(jù)傳輸主要的協(xié)議TCP 建立 比較慢。
首先,我們知道,一個TCP連接先需要握手。如果處于安全考慮使用了SSL,就還需要額外的兩個來回(客戶端重用了session的話,也需要一個額外的來回)。這些流程完畢之后,服務(wù)器才能開始往客戶端發(fā)送數(shù)據(jù)。換句話說,再小的代碼包實際上也需要幾個來回才能完成傳輸,這就讓前面描述的問題變得更加糟糕。
其次,TCP協(xié)議里面有一個流控機制,被稱為 slow start,也就是在連接建立過程中逐漸增加傳輸?shù)姆侄?segments)大小,入下圖所示:
圖2. 服務(wù)器端在TCP連接的不同階段能夠發(fā)送的分段大小(KB)
這對SPA有兩個很大的影響:
- 文件比較大的腳本,花在下載上的時間比你想象中的要長得多。Google的Ilya Grigorik在他的專著“High Performance Browser Networking” 里面說過,“4個來回(…)和數(shù)百毫秒的延遲都花在從服務(wù)器下載64KB的文件到客戶端上了”,從前面的圖也可以看到,基本是比較高速的網(wǎng)絡(luò)連接,比如倫敦和紐約之間,一個TCP連接要達(dá)到最大速度,也需要花上大概225ms。
- 因為前面說的延遲對首個頁面訪問也是有效的,所以你讓什么數(shù)據(jù)最先被傳輸就顯得非常重要了。Paul Irish在他的演講“Delivering the Goods”給出的結(jié)論是,一個Web應(yīng)用最開始的 14kb 數(shù)據(jù)是最重要的。
在足夠短的時間窗內(nèi)完成內(nèi)容傳輸(哪怕只是呈現(xiàn)基本的沒有數(shù)據(jù)的layout)的網(wǎng)站,就是響應(yīng)良好的。這也是為什么對于很多習(xí)慣了在服務(wù)器端處理數(shù)據(jù)的軟件開發(fā)者覺得Javascript很多時候根本沒必要用,或者是在很有限的情況下用用就行了。當(dāng)這些開發(fā)者使用的是配置良好的服務(wù)器和數(shù)據(jù)庫,又有CDN來做部署和分發(fā)時,他們這種感覺會非常明顯。
但是,服務(wù)器在輔助和加速頁面內(nèi)容的分發(fā)和渲染中應(yīng)該被怎么使用,也是需要根據(jù)每個應(yīng)用場景仔細(xì)分析的,絕對不是“把整個頁面交給服務(wù)器渲染吧”那么簡單的事情。在一些情況下,如果頁面上的內(nèi)容對用戶并不是非看不可的,就可以不放在第一個響應(yīng)中返回,而是讓客戶端在后面的操作中到服務(wù)器去取。
比如,有的應(yīng)用會先把一個”殼”頁面返回給客戶端,然后在這個頁面上并發(fā)的請求多個部分的數(shù)據(jù)。這樣即使在后臺連接速度較慢的情況下,仍然能夠有較好的響應(yīng)速度。還有的應(yīng)用會把 “瀏覽器里面的第一個整屏” 顯示的頁面做預(yù)渲染。
服務(wù)器能夠根據(jù)當(dāng)前處理的session,用戶和URL對腳本和樣式文件進(jìn)行分類也是很重要的。舉例來說,用來對訂單進(jìn)行分類的腳本,對于/orders這個URL顯然是重要的,而處理”首選項”的邏輯的腳本就不那么重要。再比如說,我們可以對CSS樣式表進(jìn)行分類,比如區(qū)分“結(jié)構(gòu)性的樣式”和“皮膚和模板的樣式”等。前面這類很可能對Javascript的正確運行是必須的,因此需要 阻塞 的方式加載, 后面這類則可以用異步的方式加載。
到目前為止,在服務(wù)器端處理一部分或者所有的頁面,仍然是避免過多客戶端與服務(wù)器的交互的主要手段。StackOverflow in 4096 bytes很不錯地展示了如何降低和服務(wù)器的來回交互次數(shù)。作為概念驗證的SPA,它理論上可以做到在握手后的第一個TCP連接中完成加載!當(dāng)然,要做到這些,它使用了SPDY 或者 HTTP/2 server push,因此可以在一個hop里面?zhèn)鬏斔锌蛻舳丝梢跃彺娴拇a。
圖3. 使用了內(nèi)鏈CSS和JS技術(shù)的Stackoverflow in 4096 bytes
如果我們有一個足夠靈活的系統(tǒng),可以在瀏覽器和服務(wù)器直接共享渲染頁面的代碼(比如雙方都是js),并且提供工具增量的加載腳本和樣式,那么 網(wǎng)站 和 Web應(yīng)用 就可以合一而不再是兩個模棱兩可難以區(qū)分的詞了:它們本身就有一樣的UX要素。比如一個博客頁面和一個復(fù)雜的CRM,都有URL,都需要跳轉(zhuǎn),都展示數(shù)據(jù),本質(zhì)上并沒有太大不同。即便是像數(shù)據(jù)表格這樣復(fù)雜的東西,傳統(tǒng)上主要是客戶端提供的功能來完成對數(shù)據(jù)的處理,但也首先需要給用戶展示那些需要他處理的數(shù)據(jù) 。降低客戶端和服務(wù)器交互的次數(shù),對實現(xiàn)我們說的這樣的系統(tǒng)非常重要。
在我看來,我們看到的大量系統(tǒng)上采用了這樣那樣性能上的權(quán)宜之策,是因為整個技術(shù)棧的復(fù)雜度在不斷累加。Javascript和CSS這樣的技術(shù)是被逐漸加入到系統(tǒng)的,它們的風(fēng)靡又花了一段時間。盡管有人希望在協(xié)議上做出改進(jìn),來增強性能(比如SPDY或者QUIC),但應(yīng)用層顯然才是最需要改進(jìn)的地方。
要理解速度的重要性,去重溫一下WWW和HTML創(chuàng)立之初的一些討論是非常有用的。特別是在1997年提議在HTML里加入img這個標(biāo)簽的時候,Marc Andreessen在下面這個郵件thread里反復(fù)強調(diào)了提供信息的速度有多么重要:
“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. 對用戶輸入立刻響應(yīng)
TL;DR: 我們可以使用JavaScript來掩蓋網(wǎng)絡(luò)的延遲,把它作為設(shè)計原則,就可以在你自己的應(yīng)用里面去掉絕大多數(shù)的spinner或者loading。使用PJAX和TurboLink的話,你就會失去了這些改善用戶速度體驗的機會。.
第一個原則里,在描述為什么要盡量減少前端和后端之間數(shù)據(jù)來回傳輸?shù)拇螖?shù)時,主要是基于傳輸速度有理論上限的事實。實際上另一個需要考慮的要素就是網(wǎng)絡(luò)的質(zhì)量。我們都知道,當(dāng)網(wǎng)絡(luò)連接狀況不好時,就會有數(shù)據(jù)包需要被重傳。所以,你覺得應(yīng)該一個來回就傳輸完畢的數(shù)據(jù),可能實際上要花去好幾個。
在這方面,Javascript正好可以幫上忙:通過客戶端的代碼來驅(qū)動UI,人工的構(gòu)造出零延遲,就可以掩蓋網(wǎng)絡(luò)的延遲,制造一切操作都很順暢的假象。比如,網(wǎng)頁和網(wǎng)頁之間是通過超鏈接,<a>標(biāo)簽,鏈接在一起的。傳統(tǒng)網(wǎng)頁上,當(dāng)一個鏈接被點擊時,瀏覽器就發(fā)送一個可能會耗時很久的請求,然后處理請求并把內(nèi)容呈現(xiàn)給用戶。
但Javascript允許你立刻響應(yīng)(有些地方把這個叫樂觀響應(yīng)):當(dāng)一個鏈接或者按鈕被點擊時,頁面立刻做出響應(yīng)而不需要去訪問網(wǎng)絡(luò)。這方面著名的例子就是Gmail(包括最近Google的新產(chǎn)品Inbox)的”郵件歸檔”功能。當(dāng)你點擊”歸檔”,UI上郵件立刻會被顯示為歸檔狀態(tài),而服務(wù)器的請求和處理是異步進(jìn)行的。
再比如,我們處理的是一個表單。也許你覺得一個表單在數(shù)據(jù)被提交到服務(wù)器,處理結(jié)果返回之前,不能做太多的事情。但其實當(dāng)用戶完成輸入并點擊提交的時候,我們就可以開始響應(yīng)了。甚至有些做到極致的應(yīng)用,比如Google搜索頁面,當(dāng)用戶開始輸入的時候,展示搜索結(jié)果的頁面就已經(jīng)開始渲染了。
圖4. Google在用戶輸入搜素關(guān)鍵字時就開始渲染搜索結(jié)果頁面
這種行為被稱為 layout adaptation。 它的思路是當(dāng)前頁面知道操作后狀態(tài)的頁面layout,所以在沒有數(shù)據(jù)填充的情況下,它就可以過渡到下面那個狀態(tài)的layout。這樣的處理是”樂觀”的,是因為有可能后面那個頁面的數(shù)據(jù)一直沒有返回,而這時候頁面的layout已經(jīng)畫在那里了。
Google的主頁的演進(jìn),非常清楚的說明了我們這里強調(diào)的第一和第二個原則。
首先,分析訪問www.google.com時TCP連接的包數(shù)據(jù)可以看到整個首頁的數(shù)據(jù)都被一次性發(fā)出來了。整個交互,包括關(guān)閉連接,耗時幾十毫秒而已。而且,似乎在Google一開始的版本就做到了這點。
在2004年晚些時候, Google標(biāo)桿性地使用了JavaScript完成輸入時動態(tài)提示功能(和Gmail一樣,也是一個20%創(chuàng)新時間產(chǎn)出的項目),這一功能也啟發(fā)了很多網(wǎng)站開始大量的使用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又推出了及時搜索,也就是我們前面看到的效果:當(dāng)用戶輸入關(guān)鍵字時,整個頁面無需刷新就可以展示搜索的結(jié)果。
另一個例子是iOS。在很早期的版本,iPhone就要求開發(fā)者提供一個default.png圖片,用來在應(yīng)用被加載完成之前顯示給用戶:
圖5. iPhone OS強制在應(yīng)用加載前顯示一個default.png
當(dāng)然,這里OS不是在隱藏網(wǎng)絡(luò)延遲,而是CPU處理延遲。對于iPhone初期版本來說,這樣來彌補硬件的弱點非常重要。當(dāng)然就和網(wǎng)頁上使用提前加載一樣,這種手法有可能會崩壞:當(dāng)加載來的數(shù)據(jù)和default.png不匹配的時候。Marco Arment在2010年對它可能帶來的影響進(jìn)行了 透徹的分析。
除開處理表單和輸入,Javascript還被大量用于處理文件上傳。我們可以通過各種前端表現(xiàn)來滿足用戶上傳文件的需求:拖拽,粘貼以及各種file picker。特別是有了HTML5的新API之后,我們可以在文件完成傳輸前就顯示它的信息。在Cloudup網(wǎng)站的上傳文件中,就使用了類似的實現(xiàn)。從圖片中可以看到,在用戶選擇了文件之后,縮略圖就立刻生成并顯示在用戶界面上了:
圖6. 在上傳完成前圖片就被顯示出來并且加入了虛化效果
哈爾濱品用軟件有限公司致力于為哈爾濱的中小企業(yè)制作大氣、美觀的優(yōu)秀網(wǎng)站,并且能夠搭建符合百度排名規(guī)范的網(wǎng)站基底,使您的網(wǎng)站無需額外費用,即可穩(wěn)步提升排名至首頁。歡迎體驗最佳的哈爾濱網(wǎng)站建設(shè)。
