1 知識體系
1.1 從URL輸入到頁面加載
首先我們需要通過 DNS(域名解析系統)將 URL 解析為對應的 IP 地址,然后與這個 IP 地址確定的那台服務器建立起 TCP 網絡連接,隨后我們向服務端拋出我們的 HTTP 請求,服務端處理完我們的請求之后,把目標數據放在 HTTP 響應里返回給客戶端,拿到響應數據的瀏覽器就可以開始走一個渲染的流程。渲染完畢,頁面便呈現給了用戶

將這個過程切分為如下的過程片段
- DNS 解析
- TCP 連接
- HTTP 請求拋出
- 服務端處理請求,HTTP 響應返回
- 瀏覽器拿到響應數據,解析響應內容,把解析的結果展示給用戶
1.2 性能優化思維導圖

2 網路篇(http)
輸入 URL 到顯示頁面這個過程中,涉及到網絡層面的,有三個主要過程:
- DNS 解析
- TCP 連接
- HTTP 請求/響應
2.1 DNS解析
一般來說,在前端優化中與 DNS 有關的有兩點:
- 是減少DNS的請求次數,
- 是進行DNS預獲取——DNS Prefetch
DNS 作為互聯網的基礎協議,其解析的速度似乎很容易被網站優化人員忽視。現在大多數新瀏覽器已經針對DNS解析進行了優化,典型的一次DNS解析需要耗費20-120 毫秒,減少DNS解析時間和次數是個很好的優化方式。
DNS Prefetching 是讓具有此屬性的域名不需要用戶點擊鏈接就在后台解析,而域名解析和內容載入是串行的網絡操作,所以這個方式能減少用戶的等待時間,提升用戶體驗 。
默認情況下瀏覽器會對頁面中和當前域名(正在瀏覽網頁的域名)不在同一個域的域名進行預獲取,並且緩存結果,這就是隱式的 DNS Prefetch。如果想對頁面中沒有出現的域進行預獲取,那么就要使用顯示 DNS Prefetch 了。
DNS預解析具體用法
//用meta信息來告知瀏覽器, 當前頁面要做DNS預解析 <meta http-equiv="x-dns-prefetch-control" content="on"> //在頁面header中使用link標簽來強制對DNS預解析: <link rel="dns-prefetch" href="//www.zhix.net">
注意:dns-prefetch需慎用,多頁面重復DNS預解析會增加重復DNS查詢次數,因為有開發者指出 禁用DNS 預讀取能節省每月100億的DNS查詢 。
//如果需要禁止隱式的 DNS Prefetch <meta http-equiv="x-dns-prefetch-control" content="off">
2.2 HTTP
對於 DNS 解析和 TCP 連接兩個步驟,我們前端可以做的努力非常有限。相比之下,HTTP 連接這一層面的優化才是我們網絡優化的核心
HTTP 優化有兩個大的方向
- 減少請求次數
- 減少單次請求所花費的時間
2.2.1減少HTTP請求次數
- 圖片:雪碧圖,圖標字體文件
-
雪碧圖
多張小圖片合並為一張圖,利用CSS -background-position調整圖片顯示位置 -
圖標字體文件
阿里圖標
- 合並JS和CSS文件
- webpack
- gulp
- grunt
- 瀏覽器緩存
如果圖片或者腳本,樣式文件內容比較固定,不經常被修改,那么,盡可能利用緩存技術,減少HTTP請求次數或文件下載次數
- 強緩存
- 協商緩存
2.2.2減少單次請求所花費的時間
- 圖片
圖片在線批量壓縮 - gzip
如果是vue項目,還有nginx,哪么vue,nginx,服務器都要開啟gzip
3.網絡篇(圖片優化)
3.1不同業務場景下的圖片方案選型
前置知識:二進制位數與色彩的關系 在計算機中,像素用二進制數來表示。不同的圖片格式中像素與二進制位數之間的對應關系是不同的。一個像素對應的二進制位數越多,它可以表示的顏色種類就越多,成像效果也就越細膩,文件體積相應也會越大。
3.2 JPEG/JPG
關鍵字:有損壓縮、體積小、加載快、不支持透明
-
JPG 的優點
JPG 最大的特點是有損壓縮。這種高效的壓縮算法使它成為了一種非常輕巧的圖片格式。另一方面,即使被稱為“有損”壓縮,JPG的壓縮方式仍然是一種高質量的壓縮方式:當我們把圖片體積壓縮至原有體積的 50% 以下時,JPG 仍然可以保持住 60% 的品質。此外,JPG 格式以 24 位存儲單個圖,可以呈現多達 1600 萬種顏色,足以應對大多數場景下對色彩的要求,這一點決定了它壓縮前后的質量損耗並不容易被我們人類的肉眼所察覺——前提是你用對了業務場景。
-
使用場景
JPG 適用於呈現色彩豐富的圖片,在我們日常開發中,JPG 圖片經常作為大的背景圖、輪播圖或 Banner 圖出現。
兩大電商網站對大圖的處理,是 JPG 圖片應用場景的最佳寫照: 打開淘寶首頁,我們可以發現頁面中最醒目、最龐大的圖片,一定是以 .jpg 為后綴的: 使用 JPG 呈現大圖,既可以保住圖片的質量,又不會帶來令人頭疼的圖片體積,是當下比較推崇的一種方案。
-
JPG 的缺陷
有損壓縮在上文所展示的輪播圖上確實很難露出馬腳,但當它處理矢量圖形和 Logo 等線條感較強、顏色對比強烈的圖像時,人為壓縮導致的圖片模糊會相當明顯。此外,JPEG 圖像不支持透明度處理,透明圖片需要召喚 PNG 來呈現。
3.3 png
關鍵字:無損壓縮、質量高、體積大、支持透明
-
PNG 的優點
PNG(可移植網絡圖形格式)是一種無損壓縮的高保真的圖片格式。8 和 24,這里都是二進制數的位數。按照我們前置知識里提到的對應關系,8 位的 PNG 最多支持 256 種顏色,而 24 位的可以呈現約 1600 萬種顏色。
PNG 圖片具有比 JPG 更強的色彩表現力,對線條的處理更加細膩,對透明度有良好的支持。它彌補了上文我們提到的 JPG 的局限性,唯一的 BUG 就是體積太大。
-
PNG-8 與 PNG-24 的選擇題
什么時候用 PNG-8,什么時候用 PNG-24,這是一個問題
理論上來說,當你追求最佳的顯示效果、並且不在意文件體積大小時,是推薦使用 PNG-24 的。
但實踐當中,為了規避體積的問題,我們一般不用PNG去處理較復雜的圖像。當我們遇到適合 PNG 的場景時,也會優先選擇更為小巧的 PNG-8。
如何確定一張圖片是該用 PNG-8 還是 PNG-24去呈現呢?好的做法是把圖片先按照這兩種格式分別輸出,看 PNG-8輸出的結果是否會帶來肉眼可見的質量損耗,並且確認這種損耗是否在我們(尤其是你的 UI 設計師)可接受的范圍內,基於對比的結果去做判斷。
-
應用場景
前面我們提到,復雜的、色彩層次豐富的圖片,用 PNG 來處理的話,成本會比較高,我們一般會交給 JPG 去存儲。
考慮到 PNG 在處理線條和顏色對比度方面的優勢,我們主要用它來呈現小的 Logo、顏色簡單且對比強烈的圖片或背景等。
此時我們再次把目光轉向性能方面堪稱業界楷模的淘寶首頁,我們會發現它頁面上的 Logo,無論大小,還真的都是 PNG 格式:
3.4 SVG
關鍵字:文本文件、體積小、不失真、兼容性好
-
SVG 的使用方式與應用場景
將 SVG 寫入 HTML
將 SVG 寫入獨立文件后引入 HTML將 SVG 寫入 HTML
3.5 Base64
關鍵字:文本文件、依賴編碼、小圖標解決方案
-
Base64 的應用場景
圖片的實際尺寸很小(大家可以觀察一下掘金頁面的 Base64 圖,幾乎沒有超過 2kb 的)
圖片無法以雪碧圖的形式與其它小圖結合(合成雪碧圖仍是主要的減少 HTTP 請求的途徑,Base64 是雪碧圖的補充)
圖片的更新頻率非常低(不需我們重復編碼和修改文件內容,維護成本較低)
3.6 WebP
關鍵字:年輕的全能型選手 是 Google 專為 Web 開發的一種旨在加快圖片加載速度的圖片格式,它支持有損壓縮和無損壓縮。
-
WebP 的優點
WebP 像 JPEG 一樣對細節豐富的圖片信手拈來,像 PNG 一樣支持透明,像 GIF 一樣可以顯示動態圖片——它集多種圖片文件格式的優點於一身。
-
WebP 的局限性
兼容性
3.7 總結
不同業務場景下的圖片方案選型
4.存儲篇(瀏覽器緩存)
4.1 什么是緩存
對於一個數據請求來說,可以分為發起網絡請求、后端處理、瀏覽器響應三個步驟
瀏覽器緩存可以幫助我們在第一和第三步驟中優化性能。比如說直接使用緩存而不發起請求,或者發起了請求但后端存儲的數據和前端一致,那么就沒有必要再將數據回傳回來,這樣就減少了響應數據。
緩存思維導圖

4.2 緩存位置
4.2.1 緩存優先級
從緩存位置上來說分為四種,並且各自有優先級,當依次查找緩存且都沒有命中的時候,才會去請求網絡。
Service Worker
Memory Cache
Disk Cache
Push Cache
4.2.2 Service Worker
不了解 MDN
4.2.3 MemoryCache
MemoryCache,是指存在內存中的緩存。從優先級上來說,它是瀏覽器最先嘗試去命中的一種緩存。從效率上來說,它是響應速度最快的一種緩存。
內存緩存是快的,也是“短命”的。它和渲染進程“生死相依”,當進程結束后,也就是 tab 關閉以后,內存里的數據也將不復存在。
那么哪些文件會被放入內存呢?
事實上,這個划分規則,一直以來是沒有定論的。不過想想也可以理解,內存是有限的,很多時候需要先考慮即時呈現的內存余量,再根據具體的情況決定分配給內存和磁盤的資源量的比重——資源存放的位置具有一定的隨機性
雖然划分規則沒有定論,但根據日常開發中觀察的結果,包括我們開篇給大家展示的 Network 截圖,我們至少可以總結出這樣的規律:資源存不存內存,瀏覽器秉承的是“節約原則”。我們發現,Base64 格式的圖片,幾乎永遠可以被塞進 memory cache,這可以視作瀏覽器為節省渲染開銷的“自保行為”;此外,體積不大的 JS、CSS 文件,也有較大地被寫入內存的幾率——相比之下,較大的 JS、CSS 文件就沒有這個待遇了,內存資源是有限的,它們往往被直接甩進磁盤。
4.2.4 Disk Cache
Disk Cache 也就是存儲在硬盤中的緩存,讀取速度慢點,但是什么都能存儲到磁盤中,比之 Memory Cache 勝在容量和存儲時效性上。
在所有瀏覽器緩存中,Disk Cache 覆蓋面基本是最大的。它會根據 HTTP Herder 中的字段判斷哪些資源需要緩存,哪些資源可以不請求直接使用,哪些資源已經過期需要重新請求。並且即使在跨站點的情況下,相同地址的資源一旦被硬盤緩存下來,就不會再次去請求數據。絕大部分的緩存都來自 Disk Cache,關於 HTTP 的協議頭中的緩存字段,我們會在下文進行詳細介紹
瀏覽器會把哪些文件丟進內存中?哪些丟進硬盤中 對於大文件來說,大概率是不存儲在內存中的,反之優先 當前系統內存使用率高的話,文件優先存儲進硬盤
4.2.5 Push Cache
不了解
push Cache(推送緩存)是 HTTP/2 中的內容,當以上三種緩存都沒有命中時,它才會被使用
4.3 緩存過程分析
上圖我們可以知道
- 瀏覽器每次發起請求,都會先在瀏覽器緩存中查找該請求的結果以及緩存標識
- 瀏覽器每次拿到返回的請求結果都會將該結果和緩存標識存入瀏覽器緩存中
4.4 http緩存
HTTP 緩存是我們日常開發中最為熟悉的一種緩存機制。它又分為強緩存和協商緩存。優先級較高的是強緩存,在命中強緩存失敗的情況下,才會走協商緩存。
4.5 強緩存
強緩存:不會向服務器發送請求,直接從緩存中讀取資源,在chrome控制台的Network選項中可以看到該請求返回200的狀態碼,並且Size顯示from disk cache或from memory cache。強緩存可以通過設置兩種 HTTP Header 實現:Expires 和 Cache-Control。
4.5.1 Expires
緩存過期時間,用來指定資源到期的時間,是服務器端的具體的時間點。也就是說,Expires=max-age + 請求時間,需要和Last-modified結合使用。Expires是Web服務器響應消息頭字段,在響應http請求時告訴瀏覽器在過期時間前瀏覽器可以直接從瀏覽器緩存取數據,而無需再次請求。
Expires 是 HTTP/1 的產物,受限於本地時間,如果修改了本地時間,可能會造成緩存失效。Expires: Wed, 22 Oct 2018 08:41:00 GMT表示資源會在 Wed, 22 Oct 2018 08:41:00 GMT 后過期,需要再次請求。
- 緩存過期時間,用來指定資源的到期時間,是服務器端的具體的時間點
- 告訴瀏覽器在過期時間前瀏覽器可以直接從瀏覽器緩存取數據,而不用再次請求
- max-age的優化級高於expires,當有max-age的時候,會無視expires
- 當在有效時間內,如果服務器端的文件已經發生改變,但是瀏覽器端無法感知
4.5.2 Cache-Control
在HTTP/1.1中,Cache-Control是最重要的規則,主要用於控制網頁緩存Cache-Control 可以在請求頭或者響應頭中設置,並且可以組合使用多種指令
- max-age
- s-maxage
- private
- public
- no-cache
- no-store

4.5.3 強緩存思維導圖
4.6 協商緩存
4.6.1 什么是協商緩存
協商緩存就是強制緩存失效后,瀏覽器攜帶緩存標識向服務器發起請求,由服務器根據緩存標識決定是否使用緩存的過程,主要有以下兩種情況:
協商緩存生效,返回304和Not Modified

協商緩存失效,返回200和請求結果

5. 存儲篇(本地存儲)
5.1 Cookie
- Cookie 的本職工作並非本地存儲,而是“維持狀態”。
- Cookie 不夠大
- 同一個域名下的所有請求,都會攜帶 Cookie
5.2 Local Storage,Session Storage
5.2.1 概述
存儲容量大: Web Storage 根據瀏覽器的不同,存儲容量可以達到 5-10M 之間 僅位於瀏覽器端,不與服務端發生通信。
5.2.2 Local Storage 與 Session Storage 的區別
-
生命周期: Local Storage 是持久化的本地存儲,存儲在其中的數據是永遠不會過期的,使其消失的唯一辦法是手動刪除;而 Session Storage 是臨時性的本地存儲,它是會話級別的存儲,當會話結束(頁面被關閉)時,存儲內容也隨之被釋放。
-
作用域: Local Storage、Session Storage 和 Cookie 都遵循同源策略。但 Session Storage 特別的一點在於,即便是相同域名下的兩個頁面,只要它們不在同一個瀏覽器窗口中打開,那么它們的 Session Storage 內容便無法共享。
5.2.3 應用場景
-
Local Storage
- 理論上 Cookie 無法勝任的、可以用簡單的鍵值對來存取的數據存儲任務,都可以交給 Local Storage 來做。
- 存儲一些內容穩定的資源。比如圖片內容豐富的電商網站會用它來存儲 Base64 格式的圖片字符串
- 存儲一些不經常更新的 CSS、JS 等靜態資源
-
Session Storage
微博的 Session Storage 就主要是存儲你本次會話的瀏覽足跡
5.3 IndexedDB
- IndexedDB 是沒有存儲上限的(一般來說不會小於 250M)
- IndexedDB 可以看做是 LocalStorage 的一個升級,當數據的復雜度和規模上升到了 LocalStorage 無法解決的程度,我們毫無疑問可以請出 IndexedDB 來幫忙。
6. CDN
6.1 什么是CDN
CDN (Content Delivery Network,即內容分發網絡)指的是一組分布在各個地區的服務器。這些服務器存儲着數據的副本,因此服務器可以根據哪些服務器與用戶距離最近,來滿足數據的請求。 CDN 提供快速服務,較少受高流量影響。
6.2 為什么要用 CDN
緩存、本地存儲帶來的性能提升,是不是只能在“獲取到資源並把它們存起來”這件事情發生之后?也就是說,首次請求資源的時候,這些招數都是救不了我們的。要提升首次請求的響應能力,我們還需要借助 CDN 的能力
7.渲染篇(服務端渲染)
7.1 客戶端渲染
客戶端渲染模式下,服務端會把渲染需要的靜態文件發送給客戶端,客戶端加載過來之后,自己在瀏覽器里跑一遍 JS,根據 JS 的運行結果,生成相應的 DOM
<!doctype html> <html> <head> <title>我是客戶端渲染的頁面</title> </head> <body> <div id='root'></div> <script src='index.js'></script> </body> </html>
根節點下到底是什么內容呢?你不知道,我不知道,只有瀏覽器把 index.js 跑過一遍后才知道,這就是典型的客戶端渲染。
頁面上呈現的內容,你在 html 源文件里里找不到——這正是它的特點。
7.2 服務端渲染
服務端渲染的模式下,當用戶第一次請求頁面時,由服務器把需要的組件或頁面渲染成 HTML 字符串,然后把它返回給客戶端。客戶端拿到手的,是可以直接渲染然后呈現給用戶的 HTML 內容,不需要為了生成 DOM 內容自己再去跑一遍 JS 代碼。
使用服務端渲染的網站,可以說是“所見即所得”,頁面上呈現的內容,我們在 html 源文件里也能找到
7.3 服務端渲染解決了什么性能問題
事實上,很多網站是出於效益(seo)的考慮才啟用服務端渲染,性能倒是在其次。
假設 A 網站頁面中有一個關鍵字叫“前端性能優化”,這個關鍵字是 JS 代碼跑過一遍后添加到 HTML 頁面中的。那么客戶端渲染模式下,我們在搜索引擎搜索這個關鍵字,是找不到 A 網站的——搜索引擎只會查找現成的內容,不會幫你跑 JS 代碼。A 網站的運營方見此情形,感到很頭大:搜索引擎搜不出來,用戶找不到我們,誰還會用我的網站呢?為了把“現成的內容”拿給搜索引擎看,A 網站不得不啟用服務端渲染。
但性能在其次,不代表性能不重要。服務端渲染解決了一個非常關鍵的性能問題——首屏加載速度過慢。在客戶端渲染模式下,我們除了加載 HTML,還要等渲染所需的這部分 JS 加載完,之后還得把這部分 JS 在瀏覽器上再跑一遍。這一切都是發生在用戶點擊了我們的鏈接之后的事情,在這個過程結束之前,用戶始終見不到我們網頁的廬山真面目,也就是說用戶一直在等!相比之下,服務端渲染模式下,服務器給到客戶端的已經是一個直接可以拿來呈現給用戶的網頁,中間環節早在服務端就幫我們做掉了,用戶豈不“美滋滋”?。
7.4 服務端渲染的應用場景
服務端渲染本質上是本該瀏覽器做的事情,分擔給服務器去做。這樣當資源抵達瀏覽器時,它呈現的速度就快了
但仔細想想,在這個網民遍地的時代,幾乎有多少個用戶就有多少台瀏覽器。用戶擁有的瀏覽器總量多到數不清,那么一個公司的服務器又有多少台呢?我們把這么多台瀏覽器的渲染壓力集中起來,分散給相比之下數量並不多的服務器,服務器肯定是承受不住的
除非網頁對性能要求太高了,以至於所有的招式都用完了,性能表現還是不盡人意,這時候我們就可以考慮向老板多申請幾台服務器,把服務端渲染搞起來了~
8.渲染篇(瀏覽器渲染)
8.1 瀏覽器內核
瀏覽器內核可以分成兩部分:渲染引擎(Layout Engine 或者 Rendering Engine)和 JS 引擎
渲染引擎又包括了 HTML 解釋器、CSS 解釋器、布局、網絡、存儲、圖形、音視頻、圖片解碼器等等零部件。
8.2 瀏覽器渲染過程解析
8.3 基於渲染流程的 CSS 優化建議
8.3.1 CSS 選擇符是從右到左進行匹配的
瀏覽器必須遍歷頁面上每個 li 元素,並且每次都要去確認這個 li 元素的父元素 id 是不是 myList
8.3.2 具體優化
- 避免使用通配符,只對需要用到的元素進行選擇
- 關注可以通過繼承實現的屬性,避免重復匹配重復定義。
- 少用標簽選擇器。如果可以,用類選擇器替代
- 不要畫蛇添足,id 和 class 選擇器不應該被多余的標簽選擇器拖后腿
- 減少嵌套。后代選擇器的開銷是最高的,因此我們應該盡量將選擇器的深度降到最低(最高不要超過三層),盡可能使用類來關聯每一個標簽元素
8.4 告別阻塞:CSS 與 JS 的加載順序優化
HTML、CSS 和 JS,都具有阻塞渲染的特性。 HTML 阻塞,天經地義——沒有 HTML,何來 DOM?沒有 DOM,渲染和優化,都是空談。
8.4.1 CSS 的阻塞
在剛剛的過程中,我們提到 DOM 和 CSSOM 合力才能構建渲染樹。這一點會給性能造成嚴重影響:默認情況下,CSS 是阻塞的資源。瀏覽器在構建 CSSOM 的過程中,不會渲染任何已處理的內容。即便 DOM 已經解析完畢了,只要 CSSOM 不 OK,那么渲染這個事情就不 OK(這主要是為了避免沒有 CSS 的 HTML 頁面丑陋地“裸奔”在用戶眼前)。
只有當我們開始解析 HTML 后、解析到 link 標簽或者 style 標簽時,CSS 才登場,CSSOM 的構建才開始。 很多時候,DOM 不得不等待 CSSOM。因此我們可以這樣總結:
CSS 是阻塞渲染的資源。需要將它盡早、盡快地下載到客戶端,以便縮短首次渲染的時間。
盡早(將 CSS 放在 head 標簽里)和盡快(啟用 CDN 實現靜態資源加載速度的優化)
- css文件是並行下載的
- css的下載會阻塞后面js的執行
- css的下載不會阻塞后面的html的解析,但是會阻塞dom渲染
- css的下載會阻塞后面的DOM的onload事件。
- css的下載不會阻塞后面js的下載,但是js下載完成后,被阻塞執行。
8.4.2 JS 的阻塞
JS 的作用在於修改,它幫助我們修改網頁的方方面面:內容、樣式以及它如何響應用戶交互。這“方方面面”的修改,本質上都是對 DOM 和 CSSDOM 進行修改。因此 JS 的執行會阻止 CSSOM,在我們不作顯式聲明的情況下,它也會阻塞 DOM。
JS 引擎是獨立於渲染引擎存在的。我們的 JS 代碼在文檔的何處插入,就在何處執行。當 HTML 解析器遇到一個 script 標簽時,它會暫停渲染過程,將控制權交給 JS 引擎。JS 引擎對內聯的 JS 代碼會直接執行,對外部 JS 文件還要先獲取到腳本、再進行執行。等 JS 引擎運行完畢,瀏覽器又會把控制權還給渲染引擎,繼續 CSSOM 和 DOM 的構建。 因此與其說是 JS 把 CSS 和 HTML 阻塞了,不如說是 JS 引擎搶走了渲染引擎的控制權。
