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