前端性能優化-頁面加載渲染優化

參考資料:前端總結--性能優化
參考資料:js和css的加載形成阻塞化
參考資料:瀏覽器的工做原理
參考資料:css 阻塞 js阻塞
參考資料:分佈渲染
參考資料:css加載會形成阻塞嗎
參考資料:乾貨 | 十分鐘讀懂瀏覽器渲染流程javascript

1 知識體系

1.1 從URL輸入到頁面加載


首先咱們須要經過 DNS(域名解析系統)將 URL 解析爲對應的 IP 地址,而後與這個 IP 地址肯定的那臺服務器創建起 TCP 網絡鏈接,隨後咱們向服務端拋出咱們的 HTTP 請求,服務端處理完咱們的請求以後,把目標數據放在 HTTP 響應裏返回給客戶端,拿到響應數據的瀏覽器就能夠開始走一個渲染的流程。渲染完畢,頁面便呈現給了用戶 css

將這個過程切分爲以下的過程片斷

  1. DNS 解析
  2. TCP 鏈接
  3. HTTP 請求拋出
  4. 服務端處理請求,HTTP 響應返回
  5. 瀏覽器拿到響應數據,解析響應內容,把解析的結果展現給用戶

1.2 性能優化思惟導圖


2 網路篇(http)

輸入 URL 到顯示頁面這個過程當中,涉及到網絡層面的,有三個主要過程:html

  1. DNS 解析
  2. TCP 鏈接
  3. HTTP 請求/響應

2.1 DNS解析


通常來講,在前端優化中與 DNS 有關的有兩點:前端

  1. 是減小DNS的請求次數,
  2. 是進行DNS預獲取——DNS Prefetch

DNS 做爲互聯網的基礎協議,其解析的速度彷佛很容易被網站優化人員忽視。如今大多數新瀏覽器已經針對DNS解析進行了優化,典型的一次DNS解析須要耗費20-120 毫秒,減小DNS解析時間和次數是個很好的優化方式。vue

DNS Prefetching 是讓具備此屬性的域名不須要用戶點擊連接就在後臺解析,而域名解析和內容載入是串行的網絡操做,因此這個方式能減小用戶的等待時間,提高用戶體驗 。html5

默認狀況下瀏覽器會對頁面中和當前域名(正在瀏覽網頁的域名)不在同一個域的域名進行預獲取,而且緩存結果,這就是隱式的 DNS Prefetch。若是想對頁面中沒有出現的域進行預獲取,那麼就要使用顯示 DNS Prefetch 了。java

DNS預解析具體用法webpack

//用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查詢 。nginx

//若是須要禁止隱式的 DNS Prefetch
<meta http-equiv="x-dns-prefetch-control" content="off">
複製代碼

2.2 HTTP


對於 DNS 解析和 TCP 鏈接兩個步驟,咱們前端能夠作的努力很是有限。相比之下,HTTP 鏈接這一層面的優化纔是咱們網絡優化的核心git

HTTP 優化有兩個大的方向

  1. 減小請求次數
  2. 減小單次請求所花費的時間

2.2.1減小HTTP請求次數

  • 圖片:雪碧圖,圖標字體文件
  1. 雪碧圖
    多張小圖片合併爲一張圖,利用CSS -background-position調整圖片顯示位置

  2. 圖標字體文件
    阿里圖標

  • 合併JS和CSS文件
  1. webpack
  2. gulp
  3. grunt
  • 瀏覽器緩存
    若是圖片或者腳本,樣式文件內容比較固定,不常常被修改,那麼,儘量利用緩存技術,減小HTTP請求次數或文件下載次數
  1. 強緩存
  2. 協商緩存

2.2.2減小單次請求所花費的時間

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 緩存過程分析


瀏覽器與服務器通訊的方式爲應答模式,便是:瀏覽器發起HTTP請求 – 服務器響應該請求,那麼瀏覽器怎麼肯定一個資源該不應緩存,如何去緩存呢?瀏覽器第一次向服務器發起該請求後拿到請求結果後,將請求結果和緩存標識存入瀏覽器緩存,瀏覽器對於緩存的處理是根據第一次請求資源時返回的響應頭來肯定的。具體過程以下圖:

上圖咱們能夠知道

  1. 瀏覽器每次發起請求,都會先在瀏覽器緩存中查找該請求的結果以及緩存標識
  2. 瀏覽器每次拿到返回的請求結果都會將該結果和緩存標識存入瀏覽器緩存中

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 後過時,須要再次請求。

  1. 緩存過時時間,用來指定資源的到期時間,是服務器端的具體的時間點
  2. 告訴瀏覽器在過時時間前瀏覽器能夠直接從瀏覽器緩存取數據,而不用再次請求
  3. max-age的優化級高於expires,當有max-age的時候,會無視expires
  4. 當在有效時間內,若是服務器端的文件已經發生改變,可是瀏覽器端沒法感知

4.5.2 Cache-Control

在HTTP/1.1中,Cache-Control是最重要的規則,主要用於控制網頁緩存Cache-Control 能夠在請求頭或者響應頭中設置,而且能夠組合使用多種指令

  1. max-age
  2. s-maxage
  3. private
  4. public
  5. no-cache
  6. no-store

  • max-age

    max-age=xxx (xxx is numeric)表示緩存內容將在xxx秒後失效

    1. 設置緩存存儲的最大週期,超過這個時間緩存被認爲過時(單位秒)。與Expires相反,時間是相對於請求的時間,
    2. 優先級高於Expires

  • s-maxage

    1. 覆蓋max-age 或者 Expires頭,可是僅適用於共享緩存(好比各個代理),而且私有緩存2中它被忽略
    2. 能用於public,如CDN
    3. 優先級高於max-age

  • private

    全部內容只有客戶端能夠緩存

    表示中間節點不容許緩存,對於Browser <-- proxy1 <-- proxy2 <-- Server,proxy 會老老實實把Server 返回的數據發送給proxy1,本身不緩存任何數據。當下次Browser再次請求時proxy會作好請求轉發而不是自做主張給本身緩存的數據

    1. 代表響應只能被單個用戶緩存,不能做爲共享緩存(即代理服務器不能緩存它),能夠緩存響應內容
    2. 本身的服務器
  • public

    全部內容都將被緩存(客戶端和代理服務器均可緩存)

    具體來講響應可被任何中間節點緩存,如 Browser <-- proxy1 <-- proxy2 <-- Server,中間的proxy能夠緩存資源,好比下次再請求同一資源proxy1直接把本身緩存的東西給 Browser 而再也不向proxy2要。

  • no-store

    全部內容都不會被緩存,即不使用強制緩存,也不使用協商緩存

    1. 緩存不該存儲有關客戶端請求或服務器響應的任何內容。
    2. 不會使用任何緩存策略
  • no-cache

    客戶端緩存內容,是否使用緩存則須要通過協商緩存來驗證決定。表示不使用 Cache-Control的緩存控制方式作前置驗證,而是使用 Etag 或者Last-Modified字段來控制緩存。須要注意的是,no-cache這個名字有一點誤導。設置了no-cache以後,並非說瀏覽器就再也不緩存數據,只是瀏覽器在使用緩存數據時,須要先確認一下數據是否還跟服務器保持一致

    1. 釋放緩存副本以前,強制高速緩存將請求提交給原始服務器進行驗證
    2. 這個文件無論怎麼樣,都會向服務器發起請求,去服務器哪邊詢問,這個文件有沒有在緩存策略裏

4.5.3 強緩存思惟導圖

強緩存思惟導圖

4.6 協商緩存


4.6.1 什麼是協商緩存

協商緩存就是強制緩存失效後,瀏覽器攜帶緩存標識向服務器發起請求,由服務器根據緩存標識決定是否使用緩存的過程,主要有如下兩種狀況:

協商緩存生效,返回304和Not Modified

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

4.6.2 Last-Modified和If-Modified-Since

Last-Modified 是一個響應首部,其中包含源頭服務器認定的資源作出修改的日期及時間。 它一般被用做一個驗證器來判斷接收到的或者存儲的資源是否彼此一致。因爲精確度比 ETag 要低,因此這是一個備用機制。包含有 If-Modified-Since 或 If-Unmodified-Since 首部的條件請求會使用這個字段。

  1. 基於客戶端和服務端協商的緩存機制
  2. Last-Modified ----response header
  3. If-Modified-Since----request header
  4. 須要與cache-control共同使用
  5. max-age的優先級高於Last-Modified
  • 缺點
  1. 某些服務端不能獲取精確的修改時間
  2. 文件修改時間改了,但文件內容卻沒有變

4.6.3 Etag/If-None-Match

ETagHTTP響應頭是資源的特定版本的標識符。這可讓緩存更高效,並節省帶寬,由於若是內容沒有改變,Web服務器不須要發送完整的響應。而若是內容發生了變化,使用ETag有助於防止資源的同時更新相互覆蓋(「空中碰撞」)

  1. 文件內容的hash值
  2. etag--response header
  3. if-none-match -- request header
  4. 要與cache-control共同使用

4.6.4二者對比

  1. 首先在精確度上,Etag要優於Last-Modified。
  2. 第二在性能上,Etag要遜於Last-Modified,畢竟Last-Modified只須要記錄時間,而Etag須要服務器經過算法來計算出一個hash值。
  3. 第三在優先級上,服務器校驗優先考慮Etag

4.6.5 思惟導圖

4.7 參考


瀏覽器緩存機制介紹與緩存策略剖析
一文讀懂前端緩存
深刻理解瀏覽器的緩存機制

5. 存儲篇(本地存儲)

5.1 Cookie


  1. Cookie 的本職工做並不是本地存儲,而是「維持狀態」。
  2. Cookie 不夠大
  3. 同一個域名下的全部請求,都會攜帶 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

    1. 理論上 Cookie 沒法勝任的、能夠用簡單的鍵值對來存取的數據存儲任務,均可以交給 Local Storage 來作。
    2. 存儲一些內容穩定的資源。好比圖片內容豐富的電商網站會用它來存儲 Base64 格式的圖片字符串
    3. 存儲一些不常常更新的 CSS、JS 等靜態資源
  • Session Storage

    微博的 Session Storage 就主要是存儲你本次會話的瀏覽足跡

5.3 IndexedDB


  1. IndexedDB 是沒有存儲上限的(通常來講不會小於 250M)
  2. IndexedDB 能夠看作是 LocalStorage 的一個升級,當數據的複雜度和規模上升到了 LocalStorage 沒法解決的程度,咱們毫無疑問能夠請出 IndexedDB 來幫忙。

6. CDN

6.1 什麼是CDN


CDN (Content Delivery Network,即內容分發網絡)指的是一組分佈在各個地區的服務器。這些服務器存儲着數據的副本,所以服務器能夠根據哪些服務器與用戶距離最近,來知足數據的請求。 CDN 提供快速服務,較少受高流量影響。

6.2 爲何要用 CDN


緩存、本地存儲帶來的性能提高,是否是隻能在「獲取到資源並把它們存起來」這件事情發生以後?也就是說,首次請求資源的時候,這些招數都是救不了咱們的。要提高首次請求的響應能力,咱們還須要藉助 CDN 的能力

6.3 CDN 如何工做


假設個人根服務器在杭州,同時在圖示的五個城市裏都有本身可用的機房

此時有一位北京的用戶向我請求資源。在網絡帶寬小、用戶訪問量大的狀況下,杭州的這一臺服務器或許不那麼給力,不能給用戶很是快的響應速度。因而我靈機一動,把這批資源 copy 了一批放在北京的機房裏。當用戶請求資源時,就近請求北京的服務器,北京這臺服務器低頭一看,這個資源我存了,離得這麼近,響應速度確定噌噌的!那若是北京這臺服務器沒有 copy 這批資源呢?它會再向杭州的根服務器去要這個資源。在這個過程當中,北京這臺服務器就扮演着 CDN 的角色。

6.4 CDN的核心功能


CDN 的核心點有兩個,一個是緩存,一個是回源。

  • 緩存

    「緩存」就是說咱們把資源 copy 一份到 CDN 服務器上這個過程

  • 回源

    就是說 CDN 發現本身沒有這個資源(通常是緩存的數據過時了),轉頭向根服務器(或者它的上層服務器)去要這個資源的過程。

6.5 CDN 與前端性能優化


CDN 每每被用來存放靜態資源

「根服務器」本質上是業務服務器,它的核心任務在於生成動態頁面或返回非純靜態頁面,這兩種過程都是須要計算的。業務服務器彷彿一個車間,車間裏運轉的機器轟鳴着爲咱們產出所需的資源;相比之下,CDN 服務器則像一個倉庫,它只充當資源的「棲息地」和「搬運工」。

所謂「靜態資源」,就是像 JS、CSS、圖片等不須要業務服務器進行計算即得的資源。而「動態資源」,顧名思義是須要後端實時動態生成的資源,較爲常見的就是 JSP、ASP 或者依賴服務端渲染獲得的 HTML 頁面。

什麼是「非純靜態資源」呢?它是指須要服務器在頁面以外做額外計算的 HTML 頁面。具體來講,當我打開某一網站以前,該網站須要經過權限認證等一系列手段確認個人身份、進而決定是否要把 HTML 頁面呈現給我。這種狀況下 HTML 確實是靜態的,但它和業務服務器的操做耦合,咱們把它丟到CDN 上顯然是不合適的。

6.6 CDN 的實際應用


靜態資源自己具備訪問頻率高、承接流量大的特色,所以靜態資源加載速度始終是前端性能的一個很是關鍵的指標。CDN 是靜態資源提速的重要手段,在許多一線的互聯網公司,「靜態資源走 CDN」並非一個建議,而是一個規定

www.taobao.com/

能夠看到業務服務器確實是返回給了咱們一個還沒有被靜態資源加持過的簡單 HTML 頁面

隨便點開一個靜態資源,能夠看到它都是從 CDN 服務器上請求來的

6.7 cdn與cookie


Cookie 是緊跟域名的。同一個域名下的全部請求,都會攜帶 Cookie。你們試想,若是咱們此刻僅僅是請求一張圖片或者一個 CSS 文件,咱們也要攜帶一個 Cookie 跑來跑去(關鍵是 Cookie 裏存儲的信息我如今並不須要),這是一件多麼勞民傷財的事情。Cookie 雖然小,請求卻能夠有不少,隨着請求的疊加,這樣的沒必要要的 Cookie 帶來的開銷將是沒法想象的

同一個域名下的請求會不分青紅皁白地攜帶 Cookie,而靜態資源每每並不須要 Cookie 攜帶什麼認證信息。把靜態資源和主頁面置於不一樣的域名下,完美地避免了沒必要要的 Cookie 的出現!

看起來是一個不起眼的小細節,但帶來的效用倒是驚人的。以電商網站靜態資源的流量之龐大,若是沒把這個多餘的 Cookie 拿下來,不只用戶體驗會大打折扣,每一年因性能浪費帶來的經濟開銷也將是一個很是恐怖的數字。

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 選擇符是從右到左進行匹配的

#myList li {}
複製代碼

瀏覽器必須遍歷頁面上每一個 li 元素,而且每次都要去確認這個 li 元素的父元素 id 是否是 myList

8.3.2 具體優化

  1. 避免使用通配符,只對須要用到的元素進行選擇
  2. 關注能夠經過繼承實現的屬性,避免重複匹配重複定義。
  3. 少用標籤選擇器。若是能夠,用類選擇器替代
  4. 不要多此一舉,id 和 class 選擇器不該該被多餘的標籤選擇器拖後腿
  5. 減小嵌套。後代選擇器的開銷是最高的,所以咱們應該儘可能將選擇器的深度降到最低(最高不要超過三層),儘量使用類來關聯每個標籤元素

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 實現靜態資源加載速度的優化)

  1. css文件是並行下載的
  2. css的下載會阻塞後面js的執行
  3. css的下載不會阻塞後面的html的解析,可是會阻塞dom渲染
  4. css的下載會阻塞後面的DOM的onload事件。
  5. 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 引擎搶走了渲染引擎的控制權。

  1. 現代瀏覽器會並行加載js文件。

  2. 加載或者執行js時會阻塞對標籤的解析,也就是阻塞了dom樹的造成,只有等到js執行完畢,瀏覽器纔會繼續解析標籤。沒有dom樹,瀏覽器就沒法渲染,因此當加載很大的js文件時,能夠看到頁面很長時間是一片空白

之因此會阻塞對標籤的解析是由於加載的js中可能會建立,刪除節點等,這些操做會對dom樹產生影響,若是不阻塞,等瀏覽器解析完標籤生成dom樹後,js修改了某些節點,那麼瀏覽器又得從新解析,而後生成dom樹,性能比較差

8.4.3 JS的三種加載方式

正常模式

這種狀況下 JS 會阻塞瀏覽器,瀏覽器必須等待 index.js 加載和執行完畢才能去作其它事情。

<script src="index.js"></script>
複製代碼

async(異步) 模式

async 模式下,JS 不會阻塞瀏覽器作任何其它的事情。它的加載是異步的,當它加載結束,JS 腳本會當即執行。

<script async src="index.js"></script>
複製代碼

defer(延緩) 模式

efer 模式下,JS 的加載是異步的,執行是被推遲的。等整個文檔解析完成、DOMContentLoaded 事件即將被觸發時,被標記了 defer 的 JS 文件纔會開始依次執行。

<script defer src="index.js"></script>
複製代碼

從應用的角度來講,通常當咱們的腳本與 DOM 元素和其它腳本之間的依賴關係不強時,咱們會選用 async;當腳本依賴於 DOM 元素和其它腳本的執行結果時,咱們會選用 defer。

9. 渲染篇(dom優化)

10. 渲染篇(Event Loop與異步更新策略(vue))

10.1 什麼是異步更新?


當咱們使用 Vue 或 React 提供的接口去更新數據時,這個更新並不會當即生效,而是會被推入到一個隊列裏。待到適當的時機,隊列中的更新任務會被批量觸發。這就是異步更新。

異步更新能夠幫助咱們避免過分渲染,是咱們上節提到的「讓 JS 爲 DOM 分壓」的典範之一。

10.2 異步更新的優越性


異步更新的特性在於它只看結果,所以渲染引擎不須要爲過程買單

有時咱們會遇到這樣的狀況

// 任務一
this.content = '第一次測試'
// 任務二
this.content = '第二次測試'
// 任務三
this.content = '第三次測試'
複製代碼

咱們在三個更新任務中對同一個狀態修改了三次,若是咱們採起傳統的同步更新策略,那麼就要操做三次 DOM。但本質上須要呈現給用戶的目標內容其實只是第三次的結果,也就是說只有第三次的操做是有意義的——咱們白白浪費了兩次計算。

但若是咱們把這三個任務塞進異步更新隊列裏,它們會先在 JS 的層面上被批量執行完畢。當流程走到渲染這一步時,它僅僅須要針對有意義的計算結果操做一次 DOM——這就是異步更新的妙處。

10.3 Vue狀態更新手法:nextTick


Vue在觀察到數據變化時並非直接更新DOM,而是開啓一個隊列,並緩衝在同一事件循環中發生的全部數據改變。在緩衝時會去除重複數據,從而避免沒必要要的計算和DOM操做。而後,在下一個事件循環tick中,Vue刷新隊列並執行實際工做

$nextTick就是用來知道何時DOM更新完成的

11. 渲染篇(迴流與重繪)

11.1 reflow與repaint


根據渲染樹佈局,計算CSS樣式,即每一個節點在頁面中的大小和位置等幾何信息。HTML默認是流式佈局的,CSS和js會打破這種佈局,改變DOM的外觀樣式以及大小和位置。這時就要提到兩個重要概念:replaint和reflow。

replaint:屏幕的一部分重畫,不影響總體佈局,好比某個CSS的背景色變了,但元素的幾何尺寸和位置不變。

reflow: 意味着元件的幾何尺寸變了,咱們須要從新驗證並計算渲染樹。是渲染樹的一部分或所有發生了變化。這就是Reflow,或是Layout。

因此咱們應該儘可能減小reflow和replaint,我想這也是爲何如今不多有用table佈局的緣由之一。

display:none 會觸發 reflow,visibility: hidden屬性並不算是不可見屬性,它的語義是隱藏元素,但元素仍然佔據着佈局空間,它會被渲染成一個空框,因此visibility:hidden 只會觸發 repaint,由於沒有發生位置變化。

有些狀況下,好比修改了元素的樣式,瀏覽器並不會馬上 reflow 或 repaint 一次,而是會把這樣的操做積攢一批,而後作一次 reflow,這又叫異步 reflow 或增量異步 reflow。

有些狀況下,好比 resize 窗口,改變了頁面默認的字體等。對於這些操做,瀏覽器會立刻進行 reflow。

12.2 節流與防抖的本質


這兩個東西都以閉包的形式存在。

它們經過對事件對應的回調函數進行包裹、以自由變量的形式緩存時間信息,最後用 setTimeout 來控制事件的觸發頻率。

12.3 節流


在某段時間內,無論你觸發了多少次回調,我都只認第一次,並在計時結束時給予響應。

如今有一個旅客剛下了飛機,須要用車,因而打電話叫了該機場惟一的一輛機場大巴來接。司機開到機場,心想來都來了,多接幾我的一塊兒走吧,這樣這趟才跑得值——我等個十分鐘看看。因而司機一邊打開了計時器,一邊招呼後面的客人陸陸續續上車。在這十分鐘內,後面下飛機的乘客都只能乘這一輛大巴,十分鐘過去後,無論後面還有多少沒擠上車的乘客,這班車都必須發走

12.4 防抖


在某段時間內,無論你觸發了多少次回調,我都只認最後一次。

第一個乘客上車後,司機開始計時(好比說十分鐘)。十分鐘以內,若是又上來了一個乘客,司機會把計時器清零,從新開始等另外一個十分鐘(延遲了等待)。直到有這麼一位乘客,從他上車開始,後續十分鐘都沒有新乘客上車,司機會認爲確實沒有人須要搭這趟車了,纔會把車開走。

12.5 防抖的缺憾


防抖 的問題在於它「太有耐心了」。試想,若是用戶的操做十分頻繁——他每次都不等 防抖 設置的 delay 時間結束就進行下一次操做,因而每次 防抖 都爲該用戶從新生成定時器,回調函數被延遲了不可勝數次。頻繁的延遲會致使用戶遲遲得不到響應,用戶一樣會產生「這個頁面卡死了」的觀感。 爲了不弄巧成拙,咱們須要借力 節流 的思想,打造一個「有底線」的 防抖——等你能夠,但我有個人原則:delay 時間內,我能夠爲你從新生成定時器;但只要delay的時間到了,我必需要給用戶一個響應。這個 節流與 防抖 「合體」思路,已經被不少成熟的前端庫應用到了它們的增強版 節流 函數的實現中

11 性能監控

performance

function getsec(time) {
  return time / 1000 + 's'
}
window.onload = function () {
  var per = window.performance.timing;
  console.log('DNS查詢耗時' + getsec(per.domainLookupEnd - per.domainLookupStart))
  console.log('TCP連接耗時' + getsec(per.connectEnd - per.connectStart))
  console.log('request請求響應耗時' + getsec(per.responseEnd - per.responseStart))
  console.log('dom渲染耗時' + getsec(per.domComplete - per.domInteractive))
  console.log('白屏時間' + getsec(firstPaint - pageStartTime))
  console.log('domready可操做時間' + getsec(per.domContentLoadedEventEnd - per.fetchStart))
}
複製代碼
相關文章
相關標籤/搜索