網(wǎng)站的劃分一般為二:前端和后臺(tái)。我們可以理解成后臺(tái)是用來(lái)實(shí)現(xiàn)網(wǎng)站的功能的,比如:實(shí)現(xiàn)用戶(hù)注冊(cè),用戶(hù)能夠?yàn)槲恼掳l(fā)表評(píng)論等等。而前端呢?其實(shí)應(yīng)該是屬于功能的表現(xiàn)。并且影響用戶(hù)訪問(wèn)體驗(yàn)的絕大部分來(lái)自前端頁(yè)面。
而我們建設(shè)網(wǎng)站的目的是什么呢?不就是為了讓目標(biāo)人群來(lái)訪問(wèn)嗎?所以我們可以理解成前端才是真正和用戶(hù)接觸的。除了后臺(tái)需要在性能上做優(yōu)化外,其實(shí)前端的頁(yè)面更需要在性能優(yōu)化上下功夫,只有這樣才能給我們的用戶(hù)帶來(lái)更好的用戶(hù)體驗(yàn)。就好像,好多人問(wèn),男人在找女朋友的時(shí)候是不是只看外表,一些智慧的男人給出了這樣的回答:臉蛋和身材決定了我是否想去了解她的思想,思想決定了我是否會(huì)一票否決她的臉蛋和身材。同理,網(wǎng)站也是這樣,網(wǎng)站前端的用戶(hù)體驗(yàn)決定了用戶(hù)是否想要去使用網(wǎng)站的功能,而網(wǎng)站的功能決定了用戶(hù)是否會(huì)一票否決前端體驗(yàn)。
不僅僅如此,如果前端優(yōu)化得好,他不僅可以為企業(yè)節(jié)約成本,他還能給用戶(hù)帶來(lái)更多的用戶(hù),因?yàn)樵鰪?qiáng)的用戶(hù)體驗(yàn)。說(shuō)了這么多,那么我們應(yīng)該如何對(duì)我們前端的頁(yè)面進(jìn)行性能優(yōu)化呢?
一般說(shuō)來(lái),web前端指網(wǎng)站業(yè)務(wù)邏輯之前的部分,包括瀏覽器加載、網(wǎng)站視圖模型、圖片服務(wù)、CDN服務(wù)等,主要優(yōu)化手段有瀏覽器訪問(wèn)、使用反向代理才、CDN等。
瀏覽器訪問(wèn)優(yōu)化
瀏覽器請(qǐng)求處理流程如下圖:
1、減少http請(qǐng)求,合理設(shè)置 HTTP緩存
http協(xié)議是無(wú)狀態(tài)的應(yīng)用層協(xié)議,意味著每次http請(qǐng)求都需要建立通信鏈路、進(jìn)行數(shù)據(jù)傳輸,而在服務(wù)器端,每個(gè)http都需要啟動(dòng)獨(dú)立的線程去處理。這些通信和服務(wù)的開(kāi)銷(xiāo)都很昂貴,減少http請(qǐng)求的數(shù)目可有效提高訪問(wèn)性能。
減少http的主要手段是合并CSS、合并JavaScript、合并圖片。將瀏覽器一次訪問(wèn)需要的javascript和CSS合并成一個(gè)文件,這樣瀏覽器就只需要一次請(qǐng)求。圖片也可以合并,多張圖片合并成一張,如果每張圖片都有不同的超鏈接,可通過(guò)CSS偏移響應(yīng)鼠標(biāo)點(diǎn)擊操作,構(gòu)造不同的URL。 緩存的力量是強(qiáng)大的,恰當(dāng)?shù)木彺嬖O(shè)置可以大大的減少 HTTP請(qǐng)求。假設(shè)某網(wǎng)站首頁(yè),當(dāng)瀏覽器沒(méi)有緩存的時(shí)候訪問(wèn)一共會(huì)發(fā)出 78個(gè)請(qǐng)求,共 600多 K數(shù)據(jù),而當(dāng)?shù)诙卧L問(wèn)即瀏覽器已緩存之后訪問(wèn)則僅有 10個(gè)請(qǐng)求,共 20多 K數(shù)據(jù)。 (這里需要說(shuō)明的是,如果直接 F5刷新頁(yè)面的話效果是不一樣的,這種情況下請(qǐng)求數(shù)還是一樣,不過(guò)被緩存資源的請(qǐng)求服務(wù)器是 304響應(yīng),只有 Header沒(méi)有Body,可以節(jié)省帶寬 )
怎樣才算合理設(shè)置 ?原則很簡(jiǎn)單,能緩存越多越好,能緩存越久越好。例如,很少變化的圖片資源可以直接通過(guò) HTTP Header中的Expires設(shè)置一個(gè)很長(zhǎng)的過(guò)期頭 ;變化不頻繁而又可能會(huì)變的資源可以使用 Last-Modifed來(lái)做請(qǐng)求驗(yàn)證。盡可能的讓資源能夠在緩存中待得更久。關(guān)于 HTTP緩存的具體設(shè)置和原理此處就不再詳述了。
2、使用瀏覽器緩存
對(duì)一個(gè)網(wǎng)站而言,CSS、javascript、logo、圖標(biāo)這些靜態(tài)資源文件更新的頻率都比較低,而這些文件又幾乎是每次http請(qǐng)求都需要的,如果將這些文件緩存在瀏覽器中,可以極好的改善性能。通過(guò)設(shè)置http頭中的cache-control和expires的屬性,可設(shè)定瀏覽器緩存,緩存時(shí)間可以是數(shù)天,甚至是幾個(gè)月。
在某些時(shí)候,靜態(tài)資源文件變化需要及時(shí)應(yīng)用到客戶(hù)端瀏覽器,這種情況,可通過(guò)改變文件名實(shí)現(xiàn),即更新javascript文件并不是更新javascript文件內(nèi)容,而是生成一個(gè)新的js文件并更新HTML文件中的引用。 使用瀏覽器緩存策略的網(wǎng)站在更新靜態(tài)資源時(shí),應(yīng)采用逐量更新的方法,比如需要更新10個(gè)圖標(biāo)文件,不宜把10個(gè)文件一次全部更新,而是應(yīng)該一個(gè)文件一個(gè)文件逐步更新,并有一定的間隔時(shí)間,以免用戶(hù)瀏覽器忽然大量緩存失效,集中更新緩存,造成服務(wù)器負(fù)載驟增、網(wǎng)絡(luò)堵塞的情況。
3、啟用壓縮
在服務(wù)器端對(duì)文件進(jìn)行壓縮,在瀏覽器端對(duì)文件解壓縮,可有效減少通信傳輸?shù)臄?shù)據(jù)量。如果可以的話,盡可能的將外部的腳本、樣式進(jìn)行合并,多個(gè)合為一個(gè)。文本文件的壓縮效率可達(dá)到80%以上,因此HTML、CSS、javascript文件啟用GZip壓縮可達(dá)到較好的效果。但是壓縮對(duì)服務(wù)器和瀏覽器產(chǎn)生一定的壓力,在通信帶寬良好,而服務(wù)器資源不足的情況下要權(quán)衡考慮。
4、CSS Sprites
合并 CSS圖片,減少請(qǐng)求數(shù)的又一個(gè)好辦法。
5、LazyLoad Images
這條策略實(shí)際上并不一定能減少 HTTP請(qǐng)求數(shù),但是卻能在某些條件下或者頁(yè)面剛加載時(shí)減少 HTTP請(qǐng)求數(shù)。對(duì)于圖片而言,在頁(yè)面剛加載的時(shí)候可以只加載第一屏,當(dāng)用戶(hù)繼續(xù)往后滾屏的時(shí)候才加載后續(xù)的圖片。這樣一來(lái),假如用戶(hù)只對(duì)第一屏的內(nèi)容感興趣時(shí),那剩余的圖片請(qǐng)求就都節(jié)省了。
6、CSS放在頁(yè)面最上部,javascript放在頁(yè)面最下面
瀏覽器會(huì)在下載完成全部CSS之后才對(duì)整個(gè)頁(yè)面進(jìn)行渲染,因此最好的做法是將CSS放在頁(yè)面最上面,讓瀏覽器盡快下載CSS。如果將 CSS放在其他地方比如 BODY中,則瀏覽器有可能還未下載和解析到 CSS就已經(jīng)開(kāi)始渲染頁(yè)面了,這就導(dǎo)致頁(yè)面由無(wú) CSS狀態(tài)跳轉(zhuǎn)到 CSS狀態(tài),用戶(hù)體驗(yàn)比較糟糕,所以可以考慮將CSS放在HEAD中。
Javascript則相反,瀏覽器在加載javascript后立即執(zhí)行,有可能會(huì)阻塞整個(gè)頁(yè)面,造成頁(yè)面顯示緩慢,因此javascript最好放在頁(yè)面最下面。但如果頁(yè)面解析時(shí)就需要用到j(luò)avascript,這時(shí)放到底部就不合適了。
Lazy Load Javascript(只有在需要加載的時(shí)候加載,在一般情況下并不加載信息內(nèi)容。)隨著 Javascript框架的流行,越來(lái)越多的站點(diǎn)也使用起了框架。不過(guò),一個(gè)框架往往包括了很多的功能實(shí)現(xiàn),這些功能并不是每一個(gè)頁(yè)面都需要的,如果下載了不需要的腳本則算得上是一種資源浪費(fèi) -既浪費(fèi)了帶寬又浪費(fèi)了執(zhí)行花費(fèi)的時(shí)間。目前的做法大概有兩種,一種是為那些流量特別大的頁(yè)面專(zhuān)門(mén)定制一個(gè)專(zhuān)用的 mini版框架,另一種則是 Lazy Load。
7、異步請(qǐng)求Callback(就是將一些行為樣式提取出來(lái),慢慢的加載信息的內(nèi)容)
在某些頁(yè)面中可能存在這樣一種需求,需要使用 script標(biāo)簽來(lái)異步的請(qǐng)求數(shù)據(jù)。類(lèi)似:
<span style="font-size:14px;">/*Callback 函數(shù)*/
function myCallback(info){
//do something here
}
HTML:
Callback返回的內(nèi)容 :
myCallback('Hello world!');
</span>
像以上這種方式直接在頁(yè)面上寫(xiě)<script>
對(duì)頁(yè)面的性能也是有影響的,即增加了頁(yè)面首次加載的負(fù)擔(dān),推遲了 DOMLoaded和window.onload 事件的觸發(fā)時(shí)機(jī)。如果時(shí)效性允許的話,可以考慮在 DOMLoaded事件觸發(fā)的時(shí)候加載,或者使用 setTimeout方式來(lái)靈活的控制加載的時(shí)機(jī)。
8、減少cookie傳輸
一方面,cookie包含在每次請(qǐng)求和響應(yīng)中,太大的cookie會(huì)嚴(yán)重影響數(shù)據(jù)傳輸,因此哪些數(shù)據(jù)需要寫(xiě)入cookie需要慎重考慮,盡量減少cookie中傳輸?shù)臄?shù)據(jù)量。另一方面,對(duì)于某些靜態(tài)資源的訪問(wèn),如CSS、script等,發(fā)送cookie沒(méi)有意義,可以考慮靜態(tài)資源使用獨(dú)立域名訪問(wèn),避免請(qǐng)求靜態(tài)資源時(shí)發(fā)送cookie,減少cookie傳輸次數(shù)。
9、Javascript代碼優(yōu)化
(1). DOM
a. HTML Collection(HTML收集器,返回的是一個(gè)數(shù)組內(nèi)容信息) 在腳本中 document.images、document.forms、getElementsByTagName()返回的都是HTMLCollection類(lèi)型的集合,在平時(shí)使用的時(shí)候大多將它作為數(shù)組來(lái)使用,因?yàn)樗?length屬性,也可以使用索引訪問(wèn)每一個(gè)元素。不過(guò)在訪問(wèn)性能上則比數(shù)組要差很多,原因是這個(gè)集合并不是一個(gè)靜態(tài)的結(jié)果,它表示的僅僅是一個(gè)特定的查詢(xún),每次訪問(wèn)該集合時(shí)都會(huì)重新執(zhí)行這個(gè)查詢(xún)從而更新查詢(xún)結(jié)果。所謂的“訪問(wèn)集合” 包括讀取集合的 length屬性、訪問(wèn)集合中的元素。 因此,當(dāng)你需要遍歷 HTML Collection的時(shí)候,盡量將它轉(zhuǎn)為數(shù)組后再訪問(wèn),以提高性能。即使不轉(zhuǎn)換為數(shù)組,也請(qǐng)盡可能少的訪問(wèn)它,例如在遍歷的時(shí)候可以將 length屬性、成員保存到局部變量后再使用局部變量。
b. Reflow & Repaint 除了上面一點(diǎn)之外, DOM操作還需要考慮瀏覽器的Reflow和Repaint ,因?yàn)檫@些都是需要消耗資源的。
(2). 慎用 with
with(obj){ p = 1}; 代碼塊的行為實(shí)際上是修改了代碼塊中的執(zhí)行環(huán)境 ,將obj放在了其作用域鏈的最前端,在 with代碼塊中訪問(wèn)非局部變量是都是先從 obj上開(kāi)始查找,如果沒(méi)有再依次按作用域鏈向上查找,因此使用 with相當(dāng)于增加了作用域鏈長(zhǎng)度。而每次查找作用域鏈都是要消耗時(shí)間的,過(guò)長(zhǎng)的作用域鏈會(huì)導(dǎo)致查找性能下降。 因此,除非你能肯定在 with代碼中只訪問(wèn) obj中的屬性,否則慎用 with,替代的可以使用局部變量緩存需要訪問(wèn)的屬性。
(3). 避免使用 eval和 Function
每次 eval 或Function 構(gòu)造函數(shù)作用于字符串表示的源代碼時(shí),腳本引擎都需要將源代碼轉(zhuǎn)換成可執(zhí)行代碼。這是很消耗資源的操作 —— 通常比簡(jiǎn)單的函數(shù)調(diào)用慢 100倍以上。 eval 函數(shù)效率特別低,由于事先無(wú)法知曉傳給 eval 的字符串中的內(nèi)容,eval在其上下文中解釋要處理的代碼,也就是說(shuō)編譯器無(wú)法優(yōu)化上下文,因此只能有瀏覽器在運(yùn)行時(shí)解釋代碼。這對(duì)性能影響很大。 Function 構(gòu)造函數(shù)比 eval略好,因?yàn)槭褂么舜a不會(huì)影響周?chē)a ;但其速度仍很慢。 此外,使用 eval和 Function也不利于Javascript 壓縮工具執(zhí)行壓縮。
(4). 減少作用域鏈查找
前文談到了作用域鏈查找問(wèn)題,這一點(diǎn)在循環(huán)中是尤其需要注意的問(wèn)題。如果在循環(huán)中需要訪問(wèn)非本作用域下的變量時(shí)請(qǐng)?jiān)诒闅v之前用局部變量緩存該變量,并在遍歷結(jié)束后再重寫(xiě)那個(gè)變量,這一點(diǎn)對(duì)全局變量尤其重要,因?yàn)槿肿兞刻幱谧饔糜蜴湹淖铐敹耍L問(wèn)時(shí)的查找次數(shù)是最多的。
低效率的寫(xiě)法:
<span style="font-size:14px;">// 全局變量
var globalVar = 1;
function myCallback(info){
for( var i = 100000; i--;){
//每次訪問(wèn) globalVar 都需要查找到作用域鏈最頂端,本例中需要訪問(wèn) 100000 次
globalVar += i;
}
}
</span>
更高效的寫(xiě)法:
<span style="font-size:14px;">// 全局變量
var globalVar = 1;
function myCallback(info){
//局部變量緩存全局變量
var localVar = globalVar;
for( var i = 100000; i--;){
//訪問(wèn)局部變量是最快的
localVar += i;
}
//本例中只需要訪問(wèn) 2次全局變量
在函數(shù)中只需要將 globalVar中內(nèi)容的值賦給localVar 中
globalVar = localVar;
}
</span>
此外,要減少作用域鏈查找還應(yīng)該減少閉包的使用。
(5). 數(shù)據(jù)訪問(wèn)
Javascript中的數(shù)據(jù)訪問(wèn)包括直接量 (字符串、正則表達(dá)式 )、變量、對(duì)象屬性以及數(shù)組,其中對(duì)直接量和局部變量的訪問(wèn)是最快的,對(duì)對(duì)象屬性以及數(shù)組的訪問(wèn)需要更大的開(kāi)銷(xiāo)。當(dāng)出現(xiàn)以下情況時(shí),建議將數(shù)據(jù)放入局部變量:
a. 對(duì)任何對(duì)象屬性的訪問(wèn)超過(guò) 1次
b. 對(duì)任何數(shù)組成員的訪問(wèn)次數(shù)超過(guò) 1次 .另外,還應(yīng)當(dāng)盡可能的減少對(duì)對(duì)象以及數(shù)組深度查找。
(6). 字符串拼接
在 Javascript中使用”+”號(hào)來(lái)拼接字符串效率是比較低的,因?yàn)槊看芜\(yùn)行都會(huì)開(kāi)辟新的內(nèi)存并生成新的字符串變量,然后將拼接結(jié)果賦值給新變量。與之相比更為高效的做法是使用數(shù)組的 join方法,即將需要拼接的字符串放在數(shù)組中最后調(diào)用其 join方法得到結(jié)果。不過(guò)由于使用數(shù)組也有一定的開(kāi)銷(xiāo),因此當(dāng)需要拼接的字符串較多的時(shí)候可以考慮用此方法。
10、CSS選擇符優(yōu)化
在大多數(shù)人的觀念中,都覺(jué)得瀏覽器對(duì) CSS選擇符的解析式從左往右進(jìn)行的,例如 #toc A { color: #444; }
這樣一個(gè)選擇符,如果是從右往左解析則效率會(huì)很高,因?yàn)榈谝粋€(gè) ID選擇基本上就把查找的范圍限定了,但實(shí)際上瀏覽器對(duì)選擇符的解析是從右往左進(jìn)行的。如上面的選擇符,瀏覽器必須遍歷查找每一個(gè) A標(biāo)簽的祖先節(jié)點(diǎn),效率并不像之前想象的那樣高。根據(jù)瀏覽器的這一行為特點(diǎn),在寫(xiě)選擇符的時(shí)候需要注意很多事項(xiàng),有興趣的童鞋可以去了解一下。
CDN加速
CDN(contentdistribute network,內(nèi)容分發(fā)網(wǎng)絡(luò))的本質(zhì)仍然是一個(gè)緩存,而且將數(shù)據(jù)緩存在離用戶(hù)最近的地方,使用戶(hù)以最快速度獲取數(shù)據(jù),即所謂網(wǎng)絡(luò)訪問(wèn)第一跳,如下圖。
由于CDN部署在網(wǎng)絡(luò)運(yùn)營(yíng)商的機(jī)房,這些運(yùn)營(yíng)商又是終端用戶(hù)的網(wǎng)絡(luò)服務(wù)提供商,因此用戶(hù)請(qǐng)求路由的第一跳就到達(dá)了CDN服務(wù)器,當(dāng)CDN中存在瀏覽器請(qǐng)求的資源時(shí),從CDN直接返回給瀏覽器,最短路徑返回響應(yīng),加快用戶(hù)訪問(wèn)速度,減少數(shù)據(jù)中心負(fù)載壓力。 CDN緩存的一般是靜態(tài)資源,如圖片、文件、CSS、script腳本、靜態(tài)網(wǎng)頁(yè)等,但是這些文件訪問(wèn)頻度很高,將其緩存在CDN可極大改善網(wǎng)頁(yè)的打開(kāi)速度。
反向代理
傳統(tǒng)代理服務(wù)器位于瀏覽器一側(cè),代理瀏覽器將http請(qǐng)求發(fā)送到互聯(lián)網(wǎng)上,而反向代理服務(wù)器位于網(wǎng)站機(jī)房一側(cè),代理網(wǎng)站web服務(wù)器接收http請(qǐng)求。如下圖所示:
論壇網(wǎng)站,把熱門(mén)詞條、帖子、博客緩存在反向代理服務(wù)器上加速用戶(hù)訪問(wèn)速度,當(dāng)這些動(dòng)態(tài)內(nèi)容有變化時(shí),通過(guò)內(nèi)部通知機(jī)制通知反向代理緩存失效,反向代理會(huì)重新加載最新的動(dòng)態(tài)內(nèi)容再次緩存起來(lái)。
此外,反向代理也可以實(shí)現(xiàn)負(fù)載均衡的功能,而通過(guò)負(fù)載均衡構(gòu)建的應(yīng)用集群可以提高系統(tǒng)總體處理能力,進(jìn)而改善網(wǎng)站高并發(fā)情況下的性能。