1. 程式人生 > >前端效能優化的常用手段

前端效能優化的常用手段

       在這個使用者體驗越來越重要的時代,你的頁面稍微有點卡頓,都難以挽留使用者。而作為一名有追求的前端,勢必要力所能及地優化我們前端頁面的效能。今天,就來談一談那些前端效能優化的一些常用手段。前端效能的一個重要指標是頁面載入時間,不僅事關使用者體驗,也是搜尋引擎排名考慮的一個因素。

  • 來自 Google 的資料表明,一個有 10 條資料 0.4 秒能載入完的頁面,變成 30 條資料 0.9 秒載入完之後,流量和廣告收入下降 20%。
  • Google Map 首頁檔案大小從 100KB 減小到 70-80KB 後,流量在第一週漲了 10%,接下來的三週漲了 25%。
  • 亞馬遜的資料表明:每增加 100 毫秒的載入時間,銷量就下降 1%。
  • MicrosoftOnLiveSearch的實驗表明,當搜尋結果頁面慢下來1秒時,每個使用者的查詢減少了1.0%,每個使用者的廣告點選量下降了1.5%

       以上資料更加說明「載入時間就是金錢」,前端優化主要圍繞提高載入速度進行。

 

目錄

  頁面內容                                                 伺服器

                                                                 Cookie                                

【1】減少 HTTP 請求數                         【1】使用 CDN                                                   【1】減少 Cookie 大小

【2】減少 DNS 查詢                              【2】新增 Expires 或 Cache-Control 響應頭      【2】靜態資源使用無 Cookie 域名

【3】避免重定向                                    【3】啟用 Gzip

【4】快取 Ajax 請求                              【4】配置 Etag

【5】延遲載入 | 延遲渲染                      【5】儘早輸出(flush)緩衝

【6】預先載入                                       【6】Ajax 請求使用 GET 方法

【7】減少 DOM 元素數量                     【7】避免圖片 src 為空

【8】劃分內容到不同域名

【9】儘量減少 iframe 使用

【10】避免 404 錯誤

【11】補充規則


【1】把樣式表放在 head 中                    【1】把指令碼放在頁面底部                                 【1】優化圖片

【2】不要使用 CSS 表示式                     【2】使用外部 JavaScript 和 CSS                    【2】優化 CSS Sprite

【3】使用 link標籤替代 @import             【3】壓縮 JavaScript 和 CSS                           【3】不要在 HTML 中縮放圖片

【4】不要使用 filter                                 【4】移除重複指令碼                                            【4】使用體積小、可快取的 favicon.ico

【5】減少 DOM 操作                               【5】圖片相關補充

                                                                 【6】使用高效的事件處理

 

 

頁面內容


【1】減少 HTTP 請求數

Web 前端 80% 的響應時間花在圖片、樣式、指令碼等資源下載上。瀏覽器對每個域名的連線數是有限制的,減少請求次數是縮短響應時間的關鍵。

  • 合併 JavaScript、CSS 等檔案,將瀏覽器一次訪問需要的javascript和CSS合併成一個檔案,這樣瀏覽器就只需要一次請求。
  • 使用CSS Sprite:將背景圖片合併成一個檔案,通過background-image 和 background-position 控制顯示。逐步被 Icon Font 和 SVG Sprite 取代。
  • 內容分片,將請求劃分到不同的域名上。
  • LazyLoad Images (這條策略實際上並不一定能減少 HTTP請求數,但是卻能在某些條件下或者頁面剛載入時減少 HTTP請求數)
  • 使用瀏覽器快取,將CSS、javascript、logo、圖示這些更新頻率較低的靜態資原始檔快取在瀏覽器中。

【2】減少 DNS 查詢

使用者輸入 URL 以後,瀏覽器首先要查詢域名(hostname)對應伺服器的 IP 地址,一般需要耗費 20-120 毫秒 時間。DNS 查詢完成之前,瀏覽器無法從伺服器下載任何資料。

基於效能考慮,ISP、區域網、作業系統、瀏覽器都會有相應的 DNS 快取機制。

  • IE 快取 30 分鐘,可以通過登錄檔中 DnsCacheTimeout 項設定;
  • Firefox 混存 1 分鐘,通過 network.dnsCacheExpiration 配置;
  • (TODO:補充其他瀏覽器快取資訊)

首次訪問、沒有相應的 DNS 快取時,域名越多,查詢時間越長。所以應儘量減少域名數量。但基於並行下載考慮,把資源分佈到 2 個域名上(最多不超過 4 個)。這是減少 DNS 查詢同時保證並行下載的折衷方案。

【3】避免重定向

HTTP 重定向通過 301/302 狀態碼實現。

HTTP/1.1 301 Moved Permanently
Location: http://example.com/newuri
Content-Type: text/html

客戶端收到伺服器的重定向響應後,會根據響應頭中 Location 的地址再次傳送請求。重定向會影響使用者體驗,尤其是多次重定向時,使用者在一段時間內看不到任何內容,只看到瀏覽器進度條一直在重新整理。

有時重定向無法避免,在糟糕也比丟擲 404 好。雖然通過 HTML meta refresh 和 JavaScript 也能實現,但首選 HTTP 3xx 跳轉,以保證瀏覽器「後退」功能正常工作(也利於 SEO)。

  • 最浪費的重定向經常發生、而且很容易被忽略:URL 末尾應該新增 / 但未新增。比如,訪問 http://astrology.yahoo.com/astrology 將被 301 重定向到 http://astrology.yahoo.com/astrology/(注意末尾的 /)。如果使用 Apache,可以通過 Alias 或 mod_rewrite 或 DirectorySlash 解決這個問題。
  • 網站域名變更:CNAME 結合 Alias 或 mod_rewrite 或者其他伺服器類似功能實現跳轉。

【4】快取 Ajax 請求

Ajax 可以提高使用者體驗。但「非同步」不意味著「及時」,優化 Ajax 響應速度提高效能仍是需要關注的主題。

最重要的的優化方式是快取響應結果,詳見 新增 Expires 或 Cache-Control 響應頭

以下規則也關乎 Ajax 響應速度:

【5】延遲載入 | 延遲渲染

頁面初始載入時哪些內容是絕對必需的?不在答案之列的資源都可以延遲載入。比如:

  • 非首屏使用的資料、樣式、指令碼、圖片等;
  • 使用者互動時才會顯示的內容。

遵循「漸進增強」理念開發的網站:JavaScript 用於增強用使用者體驗,但沒有(不支援) JavaScript 也能正常工作,完全可以延遲載入 JavaScript。

將首屏以外的 HTML 放在不渲染的元素中,如隱藏的 <textarea>,或者 type 屬性為非執行指令碼的 <script> 標籤中,減少初始渲染的 DOM 元素數量,提高速度。等首屏載入完成或者使用者操作時,再去渲染剩餘的頁面內容。

【6】預先載入

預先載入利用瀏覽器空閒時間請求將來要使用的資源,以便使用者訪問下一頁面時更快地響應。

無條件預先載入:頁面載入完成(load)後,馬上獲取其他資源。以 google.com 為例,首頁載入完成後會立即下載一個 Sprite 圖片,此圖首頁不需要,但是搜尋結果頁要用到。

有條件預先載入:根據使用者行為預判使用者去向,預載相關資源。比如 search.yahoo.com 開始輸入時會有額外的資源載入。Chrome 等瀏覽器的位址列也有類似的機制。

有「陰謀」的預先載入:頁面即將上線新版前預先載入新版內容。網站改版後由於快取、使用習慣等原因,會有舊版的網站更快更流暢的反饋。為緩解這一問題,在新版上線之前,舊版可以利用空閒提前載入一些新版的資源快取到客戶端,以便新版正式上線後更快的載入(好一個「心機猿」:scream:)。「雙十一」、「黑五」這類促銷日來臨之前,也可以預先下載一些相關資源到客戶端(瀏覽器、App 等),有效利用瀏覽器快取和本地儲存,降低活動當日請求壓力,提高使用者體驗。

TODO: Prefetch 相關細節: Resource Hints Spec

【7】減少 DOM 元素數量

複雜的頁面不僅下載的位元組更多,JavaScript DOM 操作也更慢。例如,同是新增一個事件處理器,500 個元素和 5000 個元素的頁面速度上會有很大區別。

從以下幾個角度考慮移除不必要的標記:

  • 是否還在使用表格佈局?
  • 塞進去更多的 <div> 僅為了處理佈局問題?也許有更好、更語義化的標記。
  • 能通過偽元素實現的功能,就沒必要新增額外元素,如清除浮動。

瀏覽器控制檯中輸入以下程式碼可以計算出頁面中有多少 DOM 元素:

document.getElementsByTagName('*').length;

Q:為什麼不使用表格佈局?

  • 更多的標籤,增加檔案大小;
  • 不易維護,無法適應響應式設計;
  • 效能考量,預設的表格佈局演算法會產生大量重繪(參見表格佈局演算法)。

【8】劃分內容到不同域名

瀏覽器一般會限制每個域的並行執行緒(一般為 6 個,甚至更少),使用不同的域名可以最大化下載執行緒,但注意保持在 2-4 個域名內,以避免 DNS 查詢損耗。

例如,動態內容放在 csspod.com 上,靜態資源放在 static.csspod.com 上。這樣還可以禁用靜態資源域下的 Cookie,減少資料傳輸,詳見 Cookie 優化

更多資訊參考 Maximizing Parallel Downloads in the Carpool Lane

【9】儘量減少 iframe 使用

使用 iframe 可以在頁面中嵌入 HTML 文件,但有利有弊。

<iframe> 優點

  • 可以用來載入速度較慢的第三方資源,如廣告、徽章;
  • 可用作安全沙箱
  • 可以並行下載指令碼。

<iframe> 缺點

  • 載入代價昂貴,即使是空的頁面;
  • 阻塞頁面 load 事件觸發。Iframe 完全載入以後,父頁面才會觸發 load 事件。 Safari、Chrome 中通過 JavaScript 動態設定 iframe src 可以避免這個問題。
  • 缺乏語義。

【10】避免 404 錯誤

HTTP 請求很昂貴,返回無效的響應(如 404 未找到)完全沒必要,降低使用者體驗而且毫無益處。

一些網站設計很酷炫、有提示資訊的 404 頁面,有助於提高使用者體驗,但還是浪費伺服器資源。尤其糟糕的是外部指令碼返回 404,不僅阻塞其他資源下載,瀏覽器還會嘗試把 404 頁面內容當作 JavaScript 解析,消耗更多資源。

【11】補充規則

定義字符集,並放在 <head> 頂部。大多數瀏覽器會暫停頁面渲染,直到找到字符集定義。

伺服器


伺服器相關優化設定可參考 H5BP 相關專案:

【1】使用 CDN

網站 80-90% 響應時間消耗在資源下載上,減少資源下載時間是效能優化的黃金髮則

相比分散式架構的複雜和巨大投入,靜態內容分發網路(CDN)可以以較低的投入,獲得載入速度有效提升。

【2】新增 Expires 或 Cache-Control 響應頭

靜態內容:將 Expires 響應頭設定為將來很遠的時間,實現「永不過期」策略;

動態內容:設定合適的 Cache-Control 響應頭,讓瀏覽器有條件地發起請求。

鑑於靜態內容和動態內容不同的快取策略,實踐中一般會把二者部署在不同的伺服器(域名)以方便管理。

Cache-Control 頭在 HTTP/1.1 規範中定義,取代了之前用來定義響應快取策略的頭(例如 Expires、Pragma)。當前的所有瀏覽器都支援 Cache-Control,因此,使用它就夠了。

參考連結

【3】啟用 Gzip

Gzip 壓縮通常可以減少 70% 的響應大小,對某些檔案更可能高達 90%,比 Deflate 更高效。主流 Web 伺服器都有相應模組,而且絕大多數瀏覽器支援 gzip 解碼。所以,應該對 HTML、CSS、JS、XML、JSON 等文字型別的內容啟用壓縮。

注意,圖片和 PDF 檔案不要使用 gzip。它們本身已經壓縮過,再使用 gzip 壓縮不僅浪費 CPU 資源,而且還可能增加檔案體積。

對於不支援的 Gzip 的使用者代理,通過設定 Vary 響應頭,返回未壓縮的資料:

Vary: *

【4】配置 Etag

Etag 通過檔案版本標識,方便伺服器判斷請求的內容是否有更新,如果沒有就響應 304,避免重新下載。

當然,啟用 Etag 可能會導致其他問題,還需要根據具體情況做判斷。(TODO:補充相關內容)

【5】儘早輸出(flush)緩衝

使用者請求頁面時,伺服器通常需要花費 200 ~ 500 毫秒來組合 HTML 頁面。在此期間,瀏覽器處於空閒、等待資料狀態。使用PHP 中的 flush() 函式,可以傳送部分已經準備好的 HTML 到瀏覽器,以便伺服器還在忙於處理剩餘頁面時,瀏覽器可以提前開始獲取資源。

可以考慮在 </head> 之後輸出一次緩衝,HTML head 一般比較容易生成,先發送以便瀏覽器開始獲取 <head> 裡引用的 CSS 等資源。

<!-- css, js -->
</head>
<?php flush(); ?>
<body>
<!-- content -->

【6】Ajax 請求使用 GET 方法

瀏覽器執行 XMLHttpRequest POST 請求時分成兩步,先發送 Header,再發送資料。而 GET 只使用一個 TCP 資料包傳送資料,所以首選 GET 方法。

根據 HTTP 規範,GET 用於獲取資料,POST 則用於向伺服器傳送資料,所以 Ajax 請求資料時使用 GET 更符合規範(GET 和 POST 對比)。

IE 中最大 URL 長度為 2K,如果超出 2K,則需要考慮使用 POST 方法。

【7】避免圖片 src 為空

圖片 src 屬性值為空字串可能以下面兩種形式出現:

HTML:

<img src="" />

JavaScript:

var img = new Image(); 
img.src = "";

雖然 src 屬性為空字串,但瀏覽器仍然會向伺服器發起一個 HTTP 請求:

  • IE 向頁面所在的目錄傳送請求;
  • Safari、Chrome、Firefox 向頁面本身傳送請求;
  • Opera 不執行任何操作。

空 src 產生請求的後果不容小覷:

  • 給伺服器造成意外的流量負擔,尤其時日 PV 較大時;
  • 浪費伺服器計算資源;
  • 可能產生報錯。

當然,瀏覽器如此實現也是根據 RFC 3986 - Uniform Resource Identifiers,當空字串作為 URI 出現時,被當成相對 URI,具體演算法參見規範 5.2 節。

空的 href 屬性也存在類似問題。使用者點選空連結時,瀏覽器也會向伺服器傳送 HTTP 請求,可以通過 JavaScript 阻止空連結的預設的行為。

Cookie


【1】減少 Cookie 大小

Cookie 被用於身份認證、個性化設定等諸多用途。Cookie 通過 HTTP 頭在伺服器和瀏覽器間來回傳送,減少 Cookie 大小可以降低其對響應速度的影響。

  • 去除不必要的 Cookie;
  • 儘量壓縮 Cookie 大小;
  • 注意設定 Cookie 的 domain 級別,如無必要,不要影響到 sub-domain;
  • 設定合適的過期時間。

更多細節參考 When the Cookie Crumbles

HTTP/2 首部壓縮在客戶端和伺服器端使用「首部表」來跟蹤和儲存之前傳送的鍵值對,對於相同的資料,不再隨每次請求和響應傳送。

【2】靜態資源使用無 Cookie 域名

靜態資源一般無需使用 Cookie,可以把它們放在使用二級域名或者專門域名的無 Cookie 伺服器上,降低 Cookie 傳送的造成的流量浪費,提高響應速度。

CSS


【1】把樣式表放在 head 中

把樣式表放在 <head> 中可以讓頁面漸進渲染,儘早呈現視覺反饋,給使用者載入速度很快的感覺。

這對內容比較多的頁面尤為重要,使用者可以先檢視已經下載渲染的內容,而不是盯著白屏等待。

如果把樣式表放在頁面底部,一些瀏覽器為減少重繪,會在 CSS 載入完成以後才渲染頁面,使用者只能對著白屏乾瞪眼,使用者體驗極差。

【2】不要使用 CSS 表示式

CSS 表示式可以在 CSS 裡執行 JavaScript,僅 IE5-IE7 支援,IE8 標準模式已經廢棄。

CSS 表示式超出預期的頻繁執行,頁面滾動、滑鼠移動時都會不斷執行,帶來很大的效能損耗。

IE7 及更低版本的瀏覽器已經逐漸成為歷史,忘記它吧。

【3】使用 link標籤替代 @import

對於 IE 某些版本,@import 的行為和 <link> 放在頁面底部一樣。所以,不要用它。

【4】不要使用 filter

AlphaImageLoader 為 IE5.5-IE8 專有的技術,和 CSS 表示式一樣,放進博物館吧。

注意:這裡所說的不是 CSS3 Filter,參考文章 Understanding CSS Filter Effects

JavaScript


【1】把指令碼放在頁面底部

瀏覽器下載指令碼時,會阻塞其他資源並行下載,即使是來自不同域名的資源。因此,最好將指令碼放在底部,以提高頁面載入速度。

一些特殊場景無法將指令碼放到頁面底部的,可以考慮 <script> 的以下屬性:

【2】使用外部 JavaScript 和 CSS

外部 JavaScript 和 CSS 檔案可以被瀏覽器快取,在不同頁面間重用,也能降低頁面大小。

當然,實際中也需要考慮程式碼的重用程度。如果僅僅是某個頁面使用到的程式碼,可以考慮內嵌在頁面中,減少 HTTP 請求數。另外,可以在首頁載入完成以後,預先載入子頁面的資源。

【3】壓縮 JavaScript 和 CSS

壓縮程式碼可以移除非功能性的字元(註釋、空格、空行等),減少檔案大小,提高載入速度。

得益於 Node.js 的流行,開源社群湧現出許多高效、易用的前端優化工具,JavaScript 和 CSS 壓縮類的,不敢說多如牛毛,多入雞毛倒是一點不誇張,如 [UglifyJS 2] (https://github.com/mishoo/UglifyJS2)、cssocssnano 等。

對於內嵌的 CSS 和 JavaScript,也可以通過 htmlmin 等工具壓縮。

這些專案都有 Gulp、Webpack 等流行構建工具的配套版本。

【4】移除重複指令碼

重複的指令碼不僅產生不必要的 HTTP 請求,而且重複解析執行浪費時間和計算資源。

【5】減少 DOM 操作

JavaScript 操作 DOM 很慢,尤其是 DOM 節點很多時。

使用時應該注意:

  • 快取已經訪問過的元素;
  • 使用 DocumentFragment 暫存 DOM,整理好以後再插入 DOM 樹;
  • 操作 className,而不是多次讀寫 style
  • 避免使用 JavaScript 修復佈局。

【6】使用高效的事件處理

  • 減少繫結事件監聽的節點,如通過事件委託;
  • 儘早處理事件,在 DOMContentLoaded 即可進行,不用等到 load 以後。

對於 resizescroll 等觸發頻率極高的事件,應該通過 debounce 等機制降低處理程式執行頻率。

TODO: 補充相關內容 http://demo.nimius.net/debounce_throttle/

圖片


【1】優化圖片

YDN 列出的相關工具 缺乏易用性,建議參考以下工具。

PNG 終極優化:

【2】優化 CSS Sprite

  • 水平排列 Sprite 中的圖片,垂直排列會增加圖片大小;
  • Spirite 中把顏色較近的組合在一起可以降低顏色數,理想狀況是低於 256 色以適用 PNG8 格式;
  • 不要在 Spirite 的影象中間留有較大空隙。減少空隙雖然不太影響檔案大小,但可以降低使用者代理把圖片解壓為畫素圖的記憶體消耗,對移動裝置更友好。

【3】不要在 HTML 中縮放圖片

不要使用 <img> 的 widthheight 縮放圖片,如果用到小圖片,就使用相應大小的圖片。

很多 CMS 和 CDN 都提供圖片裁切功能。

【4】使用體積小、可快取的 favicon.ico

Favicon.ico 一般存放在網站根目錄下,無論是否在頁面中設定,瀏覽器都會嘗試請求這個檔案。

所以確保這個圖示:

  • 存在(避免 404);
  • 儘量小,最好小於 1K;
  • 設定較長的過期時間。

對於較新的瀏覽器,可以使用 PNG 格式的 favicon。

參考連結:Favicons, Touch Icons, Tile Icons, etc. Which Do You Need?

【5】圖片相關補充

設定圖片的寬和高,以免瀏覽器按照「猜」的寬高給圖片保留的區域和實際寬高差異,產生重繪。

參考連結

 http://www.websiteoptimization.com/speed/tweak/psychology-web-performance/