jsliang 求職系列 - 23 - 性能優化

一 目錄

不折騰的前端,和鹹魚有什麼區別css

目錄
一 目錄
二 前言
2.1 DNS 解析
2.2 TCP 鏈接
2.3 發送 HTTP 請求
2.4 服務器響應
2.5 瀏覽器解析渲染頁面
2.6 其餘
2.7 小結
三 瀏覽器緩存
3.1 緩存位置
3.2 緩存機制
四 Cookie、Web Storage 和 IndexDB
4.1 Cookie
4.2 Local Storage
4.3 Session Storage
4.4 IndexDB
五 CDN
六 負載均衡
七 Webpack 優化
7.1 針對 Webpack 自己構建優化
  7.1.1 優化 resolve.modules 配置
  7.1.2 優化 resolve.extensions 配置
7.2 經過 Loader 和 Plugin 優化
  7.2.1 babel-loader
  7.2.2 tree shaking
  7.2.3 可視化分析
  7.2.4 緩存
  7.2.5 多進程
  7.2.6 抽離
  7.2.7 多進程代碼壓縮
  7.2.8 拆包
  7.2.9 打包資源壓縮
  7.2.10 按需加載
7.3 優化體驗
八 圖片優化
8.1 JPEG 與 JPG
8.2 PNG-8 與 PNG-24
8.3 GIF
8.4 SVG
8.5 Base64
8.6 雪碧圖
8.7 WebP
九 Gzip 壓縮
十 服務端渲染
10.1 客戶端渲染和服務端渲染
10.2 解決的性能問題
10.3 如何使用服務端渲染
10.4 服務端渲染小結
十一 瀏覽器渲染機制
11.1 瀏覽器渲染步驟
11.2 優化 - CSS 選擇器問題
11.3 優化 - CSS 加載問題
11.4 優化 - JS 加載問題
11.5 優化 - DOM 渲染問題
十二 預加載頁面資源
十三 長列表
13.1 懶加載
13.2 可視區域渲染
十四 性能監控
十五 參考文獻
15.1 Webpack 優化
15.2 其餘優化

二 前言

返回目錄
  • 面試官:講講前端性能優化?

要提及前端性能優化,其實咱們能夠從 「輸入 URL 到頁面呈現」 這個知識點着手講起。html

在用戶輸入 URL,按下回車以後,走過的步驟:前端

  1. DNS 解析
  2. TCP 鏈接
  3. 發送 HTTP 請求
  4. 服務器響應
  5. 瀏覽器解析渲染頁面

這其中能夠作到哪些優化呢?vue

jsliang 在這裏將這些知識點一鍋燉,看你吃下多少。node

2.1 DNS 解析

返回目錄

DNS 解析過程是一個知識點,詳細可看:計算機網絡 - DNSreact

首先須要知道的是 DNS 解析的開始步驟:瀏覽器 DNS 緩存 -> 系統緩存(host) -> 路由器緩存webpack

瀏覽器 DNS 緩存:你不肯定,也沒法幫用戶緩存;git

系統緩存(host):你本身修改 host 文件都要權限,修改用戶的就更不靠譜了;github

路由器緩存:用戶家的路由器……web

而後本地服務器向根服務器、頂級域名服務器、主域名服務器這些的請求就更不用說了,前端無法接觸。

因此這個步驟咱們忽略先。

2.2 TCP 鏈接

返回目錄

計算機網絡 - TCP 3 次握手和 4 次揮手……

這個步驟咱們也忽略,前端性能優化暫時管不到它。

2.3 發送 HTTP 請求

返回目錄

發送 HTTP 請求這塊,咱們能夠經過 4 點進行講解:

  • 瀏覽器緩存

HTTP 請求發起的時候,咱們能夠利用瀏覽器緩存,看採用強緩存仍是協商緩存,這樣咱們對於有緩存的頁面能夠快速加載。

  • Cookie 和 WebStorage

利用 CookieWebStorage 對一些可有可無的數據進行緩存,方便利用。

  • CDN 的使用

靜態資源的請求能夠採用 CDN,減小服務器壓力、防止沒必要要攜帶 Cookie 的場景等。

  • 負載均衡

利用負載均衡的特色,開啓 Node.js 方面的 PM2 或者 Nginx 的反向代理,輪詢服務器,平均各個服務器的壓力。

2.4 服務器響應

返回目錄

在服務器響應的時候,咱們也能夠作 4 部分:

  • Webpack 優化

在發佈項目到服務器以前,咱們能夠利用一些可視化插件進行分析,使用 Happypack 等提升打包效率,項目內容上能夠作按需加載、tree shaking 等。

  • 圖片優化

咱們須要熟悉瞭解 JPG/JPEGPNG-8/PNG-24GIFBase64SVG 這些圖片的特性,而後經過 Webpack 的 url-loader 將一些小圖標轉換成 Base64,一些 Icon 使用 SVG,一些輪播圖、Banner 圖用 JPG/JPGE、雪碧圖的使用等。

  • Gzip 壓縮

Gzip 壓縮的原理是在一個文本文件中找一些重複出現的字符串、臨時替換它們,從而使整個文件變小(對於圖片等會處理不了)。咱們能夠經過 Webpack 的 ComparessionPlugin 進行 Gzip 壓縮,而後在 Nginx 上進行配置,就能夠利用好 Gzip 了。

  • 服務端渲染(SSR)

服務端渲染是指瀏覽器請求的時候,服務端將已經渲染好的 HTML 頁面直接返回給瀏覽器,瀏覽器直接加載它的 HTML 渲染便可,減小了先後端交互,對 SEO 更友好。

2.5 瀏覽器解析渲染頁面

返回目錄

瀏覽器解析渲染頁面的過程是:

  1. 解析 HTML,生成 DOM
  2. 解析 CSS,生成 CSS 規則樹(CSS Rule Tree)
  3. DOM TreeCSS Rule Tree 相結合,生成 渲染樹Render Tree
  4. 從根節點開始,計算每個元素的大小、位置,給出每一個節點所應該出現的屏幕精確座標,從而獲得基於渲染樹的 佈局渲染樹Layout of the render tree)。
  5. 遍歷渲染樹,將每一個節點用 UI 渲染引擎來繪製,從而將整棵樹繪製到頁面上,這個步驟叫 繪製渲染樹Painting the render tree

關於這個步驟咱們的優化方案有:

  1. CSS 選擇器解析問題。編碼過程當中用盡量少的選擇器來表示一個元素,由於 CSS 是從右往左加載的。
  2. CSS 加載問題。儘量在 head 位置加載 CSS,減小 HTML 加載完畢須要等待 CSS 加載的問題。
  3. JS 加載問題。JS 的加載會阻塞 HTML 和 CSS 的加載,因此 script 標籤一般放 body 後面,同時能夠利用 script 標籤的 asyncdefer 屬性,同步加載 JS 或者等 HTML 和 CSS 加載渲染完後再加載 JS。
  4. DOM 渲染問題。DOM 渲染的時候可能會觸發迴流和重繪,應該儘可能避免觸發。

如何避免觸發迴流:

  1. 【CSS】使用 visibility 替換 display
  2. 【CSS】避免 table 佈局。對於 Render Tree 的計算一般只須要遍歷一次就能夠完成,可是 table 佈局須要計算屢次,一般要花 3 倍於等同元素的時間,所以要避免。
  3. 【JS】避免頻繁作 widthheight 等會觸發迴流的操做。
  4. 【JS】操做 DOM 的時候,若是是添加 DOM 節點,能夠將全部節點都在 JS 中操做完畢,再進行渲染(一次性)

2.6 其餘

返回目錄

除此以外,咱們還能夠經過:

  • Chrome 插件可視化判斷頁面哪些部分可進行優化
  • 長列表使用懶加載
  • preload 預加載頁面

等進行性能優化相關操做。

2.7 小結

返回目錄

以上,咱們就經過 6 個部分,串起來說解了前端性能優化部分的知識點。

固然,確定有咱們沒有顧及的地方,歡迎小夥伴評論留言吐槽或者私聊 jsliangjsliang 會逐步完善這塊內容。

下面咱們逐一詳細的過一下上面講到的優化知識點。

三 瀏覽器緩存

返回目錄

瀏覽器緩存能夠簡單地理解爲 HTTP 緩存。

3.1 緩存位置

返回目錄

瀏覽器緩存位置分 4 個部分:

  • Service Worker Cache - 運行在瀏覽器背後的獨立線程。通常能夠用來實現緩存功能。
  • Menory Cache - 內存中的緩存。主要是頁面上已經下載的樣式、腳本、圖片等已經抓取到的資源。
  • Disk Cache - 硬盤中的緩存。讀取速度相對慢點。
  • Push Cache - 推送緩存。 是 HTTP2 中的內容,當以上 3 種緩存都沒有命中的時候,它纔會被使用。

3.2 緩存機制

返回目錄
  • 強緩存

強緩存優先於協商緩存進行,若強制緩存生效則直接使用緩存,若不生效則進行協商緩存。強緩存不會向服務器發送請求,直接從緩存中讀取資源。

強緩存利用 HTTP 請求頭的 ExpiresCache-Control 兩個字段來控制。

  • 協商緩存

協商緩存由服務器決定是否使用緩存,若協商緩存失效,那麼該請求的緩存失效,返回 200,從新返回資源和緩存標識,再存入瀏覽器中;生效則返回 304,繼續使用緩存。

協商緩存利用 Last-Modified + If-Modified-SinceEtag + If-None-Match 來實現。

具體的緩存過程小夥伴們能夠看瀏覽器緩存篇章,這裏就不哆嗦了:

四 Cookie、Web Storage 和 IndexDB

返回目錄

4.1 Cookie

返回目錄

Cookie 最開始被設計出來其實並非來作本地存儲的,而是爲了彌補 HTTP 在狀態管理上的不足。

Cookie 本質上就是瀏覽器裏面存儲的一個很小的文本文件,內部以鍵值對的方式來存儲。

向同一個域名下發送請求,都會攜帶相同的 Cookie,服務器拿到 Cookie 進行解析,便能拿到客戶端的狀態。

缺陷:

  1. 容量缺陷。體積上線 4kb,只能存儲少許信息。
  2. 性能缺陷Cookie 請求每次都會攜帶上完整的 Cookie,隨着請求數增多,形成性能浪費。
  3. 安全缺陷。以純文本的形式在瀏覽器和服務器中傳遞,容易被非法截獲和篡改。

4.2 Local Storage

返回目錄

Local Storge 也是針對同一個域名。

同一個域名下,會存儲相同的一段 Local Storage

相比 Cookie 優點:

  1. 容量。體積上線 5M,大於 Cookie4kb
  2. 只存在客戶端。不參與和服務端的通信,避免 Cookie 的性能缺陷和安全缺陷。
  3. 接口封裝。有 setItemgetItem 兩個 API 接口。

應用場景

  • Base64 方式存儲官方 Logo 等圖片。

4.3 Session Storage

返回目錄

基本上和 Local Stoarge 一致。

相比較上的不一樣:

  • 會話級別的存儲。不一樣於 Local Storage 的持續化存儲,Session Storage 當頁面關閉的時候就不復存在了。

應用場景

  1. 對錶單信息作維護。用戶刷新頁面不丟失。
  2. 存儲本次瀏覽記錄。看過的頁面不怕找不到。

4.4 IndexDB

返回目錄

IndexedDB 是運行在瀏覽器中的 非關係型數據庫

由於本質上是數據庫,因此通常來講容量是沒有上線的。

五 CDN

返回目錄

CDN(Content Delivery Network,內容分發網絡)指的是一組分佈在各個地區的服務器。

這些服務器存儲着數據的副本,所以服務器能夠根據哪些服務器與用戶距離最近,來知足數據的請求。

CDN 提供快速服務,較少受高流量影響。

假設有一部影片出版,很是多人看。jsliang 在廣州,請求上海的服務器,結果這個服務器很是多人,資源響應地很慢。因而 jsliang 切換了路線,看到深圳服務器也有這個資源,因而向深圳服務器請求,結果能很快地看到這部影片。

在這個場景中,深圳服務器就扮演 CDN 的角色。

CDN 的核心:緩存回源

  • 緩存:將資源 copy 一份到 CDN 服務器。
  • 回源:CDN 發現本身沒有這個資源,轉頭向根服務器(上級服務器)請求這個資源。

應用場景

  1. 公司靜態資源部署到就近的服務器,利用 CDN 特性方便訪問
  2. jQuery 等框架能夠引用 CDN,加快網站的加載速度,避免同一個服務器加載的限制。
  3. 減小 Cookie 影響。同一個域名下,請求靜態資源會攜帶 Cookie 信息,可是咱們並不須要,因此使用 CDN 能夠避免沒必要要的 Cookie 出現場景。

六 負載均衡

返回目錄

若是是大型網站,負載均衡是不可或缺的內容。

  • PM2:一款 Node.js 進程管理器,讓計算機每個內核都啓動一個 Node.js 服務,而且實現自動控制負載均衡。
  • Nginx:經過輪詢機制,將用戶的請求分配到壓力較小的服務器上(反向代理)。

區別:反向代理是對服務器實現負載均衡,而 PM2 是對進程實現負載均衡。

七 Webpack 優化

返回目錄

Webpack 的優化瓶頸,主要是 2 個方面:

  • Webpack 的構建過程太花時間
  • Webpack 打包的結果體積太大

7.1 針對 Webpack 自己構建優化

返回目錄

7.1.1 優化 resolve.modules 配置

返回目錄

resolve.modules 用於配置 Webpack 去哪些目錄下尋找第三方模塊,默認是 ['node_modules'],可是,它會先去當前目錄的 ./node_modules 查找,沒有的話再去 ../node_modules,最後到根目錄。

因此能夠直接指定項目根目錄,就不須要一層一層查找。

resolve: {
  modules: [path.resolve(__dirname, 'node_modules')],
}

7.1.2 優化 resolve.extensions 配置

返回目錄

在導入沒帶文件後綴的路徑時,Webpack 會自動帶上後綴去嘗試詢問文件是否存在,而 resolve.extensions 用於配置嘗試後綴列表;默認爲 extensions:['js', 'json']

當遇到 require('./data')Webpack 會先嚐試尋找 data.js,沒有再去找 data.json;若是列表越長,或者正確的後綴越日後,嘗試的次數就會越多。

因此在配置時爲提高構建優化需遵照:

  1. 頻率出現高的文件後綴優先放在前面。
  2. 列表儘量的少,例如只有 3 個:jsjsxjson
  3. 書寫導入語句時,儘可能寫上後綴名。

7.2 經過 Loader 和 Plugin 優化

返回目錄

7.2.1 babel-loader

返回目錄

babel-loader 爲例,能夠經過 includeexclude 幫助咱們避免 node_modules 這類龐大文件夾。

7.2.2 tree shaking

返回目錄

經過 ES6 的 import/export 來檢查未引用代碼,以及 sideEffects 來標記無反作用代碼,最後用 UglifyJSPlugin 來作 tree shaking,從而刪除冗餘代碼。

7.2.3 可視化分析

返回目錄
  • speed-measure-webpack-plugin:測量出在構建過程當中,每個 Loader 和 Plugin 的執行時長。
  • webpack-bundle-analyzer:經過矩陣樹圖的方式將包內各個模塊的大小和依賴關係呈現出來。
  • webpack-chart
  • webpack-analyse

7.2.4 緩存

返回目錄
  • cache-loader

參考連接:cache-loader

babel-loader 開啓 cache 後,將 loader 的編譯結果寫進硬盤緩存,再次構建若是文件沒有發生變化則會直接拉取緩存。

  • uglifyjs-webpack-plugin

也能夠解決緩存問題。

7.2.5 多進程

返回目錄

Happypack 能夠將任務分解成多個子進程去併發執行,大大提高打包效率。

7.2.6 抽離

返回目錄

經過 DllPlugin 或者 Externals 進行靜態依賴包的分離。

因爲 CommonsChunkPlugin 每次構建會從新構建一次 vendor,因此出於效率考慮,使用 DllPlugin 將第三方庫單獨打包到一個文件中,只有依賴自身發生版本變化時纔會從新打包。

7.2.7 多進程代碼壓縮

返回目錄

由於自帶的 UglifyJsPlugin 壓縮插件是單線程運行的,而 ParallelUglifyPlugin 能夠並行執行。

因此經過 ParallelUglifyPlugin 代替自帶的 UglifyJsPlugin 插件。

7.2.8 拆包

返回目錄

Webpack 中,到底什麼是代碼分離?代碼分離容許你把代碼拆分到多個文件中。若是使用得當,你的應用性能會提升不少。由於瀏覽器能緩存你的代碼。

每當你作出一次修改,包含修改的文件須要被全部訪問你網站的人從新下載。但你並不會常常修改應用的依賴庫。

若是你能把那些依賴庫拆分到徹底分離的文件中,即便業務邏輯發生了更改,訪問者也不須要再次下載依賴庫,直接使用以前的緩存就能夠了。

因爲有了 SplitChunksPlugin,你能夠把應用中的特定部分移至不一樣文件。若是一個模塊在不止一個 chunk 中被使用,那麼利用代碼分離,該模塊就能夠在它們之間很好地被共享。

7.2.9 打包資源壓縮

返回目錄
  • JS 壓縮:UglifyJSPlugin
  • HTML 壓縮:HtmlWebpackPlugin
  • 提取公共資源:splitChunks.cacheGroups
  • CSS 壓縮:MiniCssExtractPlugin
  • Gzip 壓縮:不包括圖片

7.2.10 按需加載

返回目錄

經過 Code-Splitting 來作 React 的按需加載.

Code_Splitting 核心是 require-ensure

7.3 優化體驗

返回目錄
  • progress-bar-webpack-plugin:在終端底部,將會有一個構建的進度條,可讓你清晰的看見構建的執行進度。
  • webpack-build-notifier:在構建完成時,可以像微信、Lark 這樣的 APP 彈出消息的方式,提示構建已經完成。
  • webpack-dashboard:對 Webpack 原始的構建輸出不滿意的話,也可使用這樣一款 Plugin 來優化你的輸出界面。

八 圖片優化

返回目錄

8.1 JPEG 與 JPG

返回目錄
  • 關鍵字:有損壓縮、體積小、加載快、不支持透明
  • 優勢:壓縮必定程度能保持品質、體積小、請求速度快
  • 缺點:處理矢量圖形、Logo 等線條感較強,顏色對比強烈的圖形,人爲壓縮會致使圖片模糊明顯。不支持透明度處理。
  • 使用場景:大的背景圖、輪播圖或者 Banner 圖。

8.2 PNG-8 與 PNG-24

返回目錄
  • 關鍵字:無損壓縮、質量高、體積大、支持透明
  • 優勢:PNG-8 支持 256 種顏色,PNG-24 支持 1600 種顏色。更強的色彩表現力,對線條的處理更加細膩,對透明度有良好的支持。
  • 缺點:體積較大
  • 使用場景:Logo、顏色簡單且對比強烈的圖片和背景。

8.3 GIF

返回目錄
  • 關鍵字:動態圖、體積小支持透明
  • 優勢:能夠壓縮體積很是小。可插入多幀實現動畫效果。支持透明色浮現於背景之上。
  • 缺點:最多隻能處理 256 中顏色,不適用於真彩圖像。
  • 使用場景:小動畫。

8.4 SVG

返回目錄
  • 關鍵字:文本文件、體積小、不失真、兼容性好
  • 優勢:文本體積更小,可壓縮性更強。圖片能夠無限放大不失真。文本文件能夠直接在 HTML 中寫入,靈活性高。
  • 缺點:渲染成本高、學習成本(可編程)
  • 使用場景:變成代碼嵌入 HTML 中,也能夠換成 .svg 後綴的文件進行引用。

8.5 Base64

返回目錄
  • 關鍵字:文本文件、依賴編碼、小圖標解決方案
  • 優勢:做爲雪碧圖的補充而存在,減小加載頁面圖片時對服務器的請求次數。(img src 會發起資源請求,可是 Base64 獲得的是字符串,嵌入 HTML 中)
  • 缺點:大圖使用 Base64 會增大致積,影響性能
  • 使用場景:小 Logo(不超過 2kb)、更新頻率低的圖片。
  • 編碼工具:Webpack 的 url-loader 能夠根據文件大小來判斷是否編碼成 Base64。

8.6 雪碧圖

返回目錄

雪碧圖、CSS 精靈、CSS Sprites、圖像精靈,都是同一個玩意。

它是將小圖標和背景圖像合併到一張圖片上,而後經過 CSS 背景定位來顯示其中的每個具體部分。

它是一種優化手段,由於單張圖片所需的 HTTP 請求更少,對內存和帶寬更加友好。

8.7 WebP

返回目錄
  • 關鍵字:年輕的全能型選手
  • 優勢:支持有損壓縮和無損壓縮、支持透明、能夠跟 GIF 同樣顯示動態圖
  • 缺點:兼容性差
  • 使用場景:暫無大型應用場景

九 Gzip 壓縮

返回目錄
  • Webpack 開啓 Gzip

經過 compression-webpack-plugin 能夠開啓 Gzip 壓縮。

  • 是否值得開啓 Gzip

若是壓縮文件過小,那不使用;可是若是具備必定規模的項目文件,能夠開啓 Gzip。

  • Gzip 原理

Gzip 並非萬能的,它的原理是在一個文本文件中找一些重複出現的字符串、臨時替換它們,從而使整個文件變小,因此對於圖片等會處理不了。

  • 服務器端和 Webpack 的 Gzip 並存

服務器壓縮也須要時間開銷和 CPU 開銷,因此有時候能夠用 Webpack 來進行 Gzip 壓縮,從而爲服務器分壓。

十 服務端渲染

返回目錄
  • 什麼是服務端渲染(服務端渲染的運行機制)
  • 爲何要用服務端渲染(服務端渲染解決了什麼性能問題)
  • 怎麼作服務端渲染(服務端渲染的應用實例和使用場景)

10.1 客戶端渲染和服務端渲染

返回目錄

客戶端渲染中,頁面上呈現的內容,在 HTML 源文件中每每找不到。

而服務端渲染,當用戶第一次請求頁面時,服務器會把須要的組件或者頁面渲染成 HTML 字符串,返回給客戶端。

即客戶端直接拿到 HTML 內容,而不須要跑一遍 JS 去生成 DOM 內容。

「所見即所得」,服務端渲染情景下,頁面上呈現的內容,在 HTML 源文件裏面也能夠找到。

10.2 解決的性能問題

返回目錄

假設 A 網站關鍵字上有 前端性能優化,可是這篇文章只有 A 網站服務器搜索事後纔會出來結果,這時候搜索引擎是沒法找到的。

爲了更好的 SEO 效果,就要拿 「現成的內容」 給搜索引擎看,就要開啓服務端渲染。

其次,服務端渲染解決了一個性能問題 —— 首屏加載速度過慢。

從輸入 URL 到頁面渲染過程當中咱們知道,若是是客戶端渲染,咱們須要加載 HTML、CSS,而後再通過 JS 造成 Render Tree,定位後再繪製頁面。

這個過程當中用戶一直在等待,若是採用了服務端渲染,那麼服務端能夠直接給一個能夠拿來呈現給用戶的頁面。

10.3 如何使用服務端渲染

返回目錄
  • 如何給 React 開啓服務端渲染
  • 如何給 Vue 開啓服務端渲染

給 React 開啓:

前端項目 - VDOM.js
import React from 'react';

const VDom = () => {
  return <div>我是一個被渲染爲真實 DOM 的虛擬 DOM</div>
};

export default VDom;
Node 項目 - index.js
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import VDom from './VDom';

// 建立一個 express 應用
const app = express();

// renderToString 是把虛擬 DOM 轉化爲真實 DOM 的關鍵方法
const RDom = renderToString(<VDom />);

// 編寫 HTML 模板,插入轉化後的真實 DOM 內容
const Page = `
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <span>服務端渲染出了真實 DOM:  </span>
    ${RDom}
  </body>
</html>
`;
            
// 配置 HTML 內容對應的路由
app.get('/index', function(req, res) {
  res.send(Page)
});

// 配置端口號
const server = app.listen(8000);

VDom 組件已經被 renderToString 轉化爲了一個內容爲 <div data-reactroot="">我是一個被渲染爲真實 DOM 的虛擬 DOM</div> 的字符串,這個字符串被插入 HTML 代碼,成爲了真實 DOM 樹的一部分。

至於 Vue 的能夠看:服務器端渲染 (SSR)?

不熟悉 Vue,就不哆嗦了。

10.4 服務端渲染小結

返回目錄

SSR 主要用於解決單頁應用首屏渲染慢以及 SEO 問題,同時也解決了與後端同窗的溝通成本。但同時:提升了服務器壓力,吃 CPU,內存等資源,優化很差提升成本。

十一 瀏覽器渲染機制

返回目錄

瀏覽器內核決定了瀏覽器解釋網頁語法的方式。

目前常見的瀏覽器內核有:Trident(IE)、Gecko(火狐)、Blink(Chrome、Opera)、Webkit(Safari)。

11.1 瀏覽器渲染步驟

返回目錄

如上圖,瀏覽器的渲染過程爲:

  1. 解析 HTML,生成 DOM
  2. 解析 CSS,生成 CSS 規則樹(CSS Rule Tree)
  3. DOM TreeCSS Rule Tree 相結合,生成 渲染樹Render Tree
  4. 從根節點開始,計算每個元素的大小、位置,給出每一個節點所應該出現的屏幕精確座標,從而獲得基於渲染樹的 佈局渲染樹Layout of the render tree)。
  5. 遍歷渲染樹,將每一個節點用 UI 渲染引擎來繪製,從而將整棵樹繪製到頁面上,這個步驟叫 繪製渲染樹Painting the render tree

11.2 優化 - CSS 選擇器問題

返回目錄

咱們正常的閱讀順序是從左往右的,可是 CSS 解析器解析 CSS 的時候,採用的是古人的規則。

#ul li {}

這樣的一行規則,咱們寫起來的時候很順暢:先找 idul 的元素,再找裏面的 li 元素。

可是實際上 CSS 解析器是從右往左的,它會先查找全部 li 元素,而且逐個確認這個 li 元素的父元素的 id 是否是 ul,這就坑死了。

因此像通配符 * { padding: 0; margin: 0 } 這種,小夥伴們就應該減小設置,要否則頁面的元素越多遍歷匹配越久。

總結一下:

  • 避免使用通配符 * 等。
  • 減小使用標籤選擇器,用類選擇器或者標籤選擇器替代,例如 span 替換爲 .span
  • 減小嵌套匹配,例如 #ul li a

11.3 優化 - CSS 加載問題

返回目錄

爲了不 HTML 解析完畢,可是 CSS 沒有解析完畢,從而致使頁面直接 「裸奔」 在用戶面前的問題,瀏覽器在處理 CSS 規則樹的時候,不會渲染任何已處理的內容。

因此不少時候,咱們會讓網頁儘早處理 CSS,即在 head 標籤中啓用 link 或者啓用 CDN 實現靜態資源加載速度的優化。

11.4 優化 - JS 加載問題

返回目錄

在上面的加載過程當中咱們並無提到 JS,實際上 JS 會對 DOM 和 CSSDOM 進行修改,所以 JS 的執行會阻止 CSS 規則樹的解析,有時候還會阻塞 DOM。

實際上,當 HTML 解析器趕上 script 標籤時,它會暫停解析過程,將控制器交給 JS 引擎。

若是是內部的 JS 代碼,它會直接執行,可是若是是 src 引入的,還要先獲取腳本,再進行執行。

等 JS 引擎執行完畢後,再交接給渲染引擎,繼續 HTML 樹和 CSS 規則樹的構建。

這樣一來一回交接,並且有時候 JS 執行過多還會卡慢,進而致使頁面渲染變慢。

因此咱們能夠經過 async 異步加載完 JS 腳本,再執行裏面內容;或者經過 defer 等整個文檔解析完畢後,再執行這個 JS 文件。

若是 JS 和 DOM 元素或者其餘 JS 代碼之間的依賴不強的時候,使用 async

若是 JS 依賴於 DOM 元素和其餘 JS 的執行結果,那就使用 defer

11.5 優化 - DOM 渲染問題

返回目錄

當使用 JS 去操做 DOM 的時候,其實是 JS 引擎和渲染引擎之間的溝通,這個溝通的過程要開銷的。

每操做一次 DOM 就收費一次,多了頁面就卡起來咯。

同時,操做 DOM 的時候修改了尺寸等元素,還會引發迴流和重繪。

  • 迴流(reflow):又叫重排(layout)。當元素的尺寸、結構或者觸發某些屬性時,瀏覽器會從新渲染頁面,稱爲迴流。此時,瀏覽器須要從新通過計算,計算後還須要從新頁面佈局,所以是較重的操做。
  • 重繪(repaint):當元素樣式的改變不影響佈局時,瀏覽器將使用重繪對元素進行更新,此時因爲只須要 UI 層面的從新像素繪製,所以損耗較少

什麼操做觸發迴流?

  1. 添加刪除 DOM 元素
  2. 改變邊框、邊距、寬高(bordermarginpaddingwidthheight
  3. 瀏覽器改變窗口(resize
  4. ……等

什麼操做觸發重繪?

  1. 修改背景色、顏色(backgroundcolor
  2. 設置可見度(visibility
  3. 設置背景圖(background-image
  4. ……等

咱們仔細看這張圖,能夠看到重排(Layout)會致使 Render Tree 重構,進而觸發重繪(Painting):

  • 迴流一定重繪,重繪不必定迴流

所以,咱們操做 DOM 的時候,能夠這麼優化:

  1. 【CSS】使用 visibility 替換 display
  2. 【CSS】避免 table 佈局。對於 Render Tree 的計算一般只須要遍歷一次就能夠完成,可是 table 佈局須要計算屢次,一般要花 3 倍於等同元素的時間,所以要避免。
  3. 【JS】避免頻繁作 widthheight 等會觸發迴流的操做。
  4. 【JS】操做 DOM 的時候,若是是添加 DOM 節點,能夠將全部節點都在 JS 中操做完畢,再進行渲染(一次性)

十二 預加載頁面資源

返回目錄

preload 提供了一種聲明式的命令,讓瀏覽器提早加載指定資源(加載後並不執行),在須要執行的時候再執行。

提供的好處主要是:

  • 將加載和執行分離開,可不阻塞渲染和 documentonload 事件
  • 提早加載指定資源,再也不出現依賴的 font 字體隔了一段時間才刷出
<!-- 使用 link 標籤靜態標記須要預加載的資源 -->
<link rel="preload" href="/path/to/style.css" as="style">

<!-- 或使用腳本動態建立一個 link 標籤後插入到 head 頭部 -->
<script>
  const link = document.createElement('link');
  link.rel = 'preload';
  link.as = 'style';
  link.href = '/path/to/style.css';
  document.head.appendChild(link);
</script>

在不支持 preload 的瀏覽器環境中,會忽略對應的 link 標籤。

區分 preloadprefetch

  • preload:告訴瀏覽器頁面一定須要的資源,瀏覽器必定會加載這些資源。
  • prefetch:告訴瀏覽器頁面可能須要的資源,瀏覽器不必定會加載這些資源。

固然,開發中須要注意:

  • 避免濫用 preload
  • 避免混用 preloadprefetch
  • 避免錯用 preload 加載跨域資源

十三 長列表

返回目錄

13.1 懶加載

返回目錄

懶加載實現思路:

  • div 經過背景圖片設置爲 none,起到佔位的做用。
  • 出如今可視區域的時候,div 填寫有效 URL。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Lazy-Load</title>
  <style>
    .img {
      width: 100px;
      height: 300px;
    }
    .img img {
      width: 200px;
      height: 400px;
    }
  </style>
</head>
<body>
  <div class="container">
    <!-- 注意咱們並無爲它引入真實的 src -->
    <div class="img"><img class="pic" alt="加載中" data-src="https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png"></div>
    <div class="img"><img class="pic" alt="加載中" data-src="https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png"></div>
    <div class="img"><img class="pic" alt="加載中" data-src="https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png"></div>
    <div class="img"><img class="pic" alt="加載中" data-src="https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png"></div>
    <div class="img"><img class="pic" alt="加載中" data-src="https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png"></div>
    <div class="img"><img class="pic" alt="加載中" data-src="https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png"></div>
    <div class="img"><img class="pic" alt="加載中" data-src="https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png"></div>
    <div class="img"><img class="pic" alt="加載中" data-src="https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png"></div>
  </div>

  <script>
    (function() {
      // 獲取全部的圖片標籤
      const imgs = document.getElementsByTagName('img');

      // 獲取可視區域的高度(document.documentElement.clientHeight 是兼容低版本 IE)
      const viewHeight = window.innerHeight || document.documentElement.clientHeight;

      // num 用於統計當前顯示到了哪一張圖片,避免每次都從第一張圖片開始檢查是否露出
      let num = 0;

      const lazyload = () => {
        for (let i = num; i < imgs.length; i++) {

          // 用可視區域高度減去元素頂部距離可視區域頂部的高度
          let distance = viewHeight - imgs[i].getBoundingClientRect().top;

          // 若是可視區域高度大於等於元素頂部距離可視區域頂部的高度,說明元素露出
          if (distance >= 0){

            // 給元素寫入真實的 src,展現圖片
            imgs[i].src = imgs[i].getAttribute('data-src');

            // 前 i 張圖片已經加載完畢,下次從第 i + 1 張開始檢查是否露出
            num = i + 1;
          }
        }
      }

      // 首屏初始化
      lazyload();

      // 監聽Scroll事件
      window.addEventListener('scroll', lazyload, false);
    })()
  </script>
</body>
</html>

還有其餘方法,諸如:

  • img 標籤自帶的 loading 屬性
  • InsectionObserver
  • 骨架屏

13.2 可視區域渲染

返回目錄

無限滾動在移動端很常見,可是可見區域渲染並不常見,主要是由於 IOS 上 UIWebView 的 onscroll 並不能實時觸發。

實現可見區域渲染的思路:

  1. 計算當前可見區域起始數據的 startIndex
  2. 計算當前可見區域結束數據的 endIndex
  3. 計算當前可見區域的數據,並渲染到頁面中
  4. 計算 startIndex 對應的數據在整個列表中的偏移位置 startOffset,並設置到列表上

代碼實現:略。

十四 性能監控

返回目錄
  1. Chrome 工具 Performance
  2. Chrome 插件 Page Speed
  3. 自動化工具 Lighthouse

    1. Chrome 拓展安裝
    2. npm i lighthouse -glighthouse https://www.baidu.com
    3. Chrome 有基於 LightHouseAudits 面板

十五 參考文獻

返回目錄

本篇參考文獻有 31 篇。

15.1 Webpack 優化

返回目錄

2019 年文章

2018 年文章

2017 年文章

15.2 其餘優化

返回目錄

jsliang 的文檔庫由 梁峻榮 採用 知識共享 署名-非商業性使用-相同方式共享 4.0 國際 許可協議 進行許可。<br/>基於 https://github.com/LiangJunrong/document-library 上的做品創做。<br/>本許可協議受權以外的使用權限能夠從 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 處得到。
相關文章
相關標籤/搜索