不折騰的前端,和鹹魚有什麼區別css
返回目錄
要提及前端性能優化,其實咱們能夠從 「輸入 URL 到頁面呈現」 這個知識點着手講起。html
在用戶輸入 URL
,按下回車以後,走過的步驟:前端
這其中能夠作到哪些優化呢?vue
jsliang 在這裏將這些知識點一鍋燉,看你吃下多少。node
返回目錄
DNS 解析過程是一個知識點,詳細可看:計算機網絡 - DNSreact
首先須要知道的是 DNS
解析的開始步驟:瀏覽器 DNS 緩存 -> 系統緩存(host) -> 路由器緩存webpack
瀏覽器 DNS
緩存:你不肯定,也沒法幫用戶緩存;git
系統緩存(host
):你本身修改 host
文件都要權限,修改用戶的就更不靠譜了;github
路由器緩存:用戶家的路由器……web
而後本地服務器向根服務器、頂級域名服務器、主域名服務器這些的請求就更不用說了,前端無法接觸。
因此這個步驟咱們忽略先。
返回目錄
這個步驟咱們也忽略,前端性能優化暫時管不到它。
返回目錄
發送 HTTP
請求這塊,咱們能夠經過 4
點進行講解:
HTTP
請求發起的時候,咱們能夠利用瀏覽器緩存,看採用強緩存仍是協商緩存,這樣咱們對於有緩存的頁面能夠快速加載。
利用 Cookie
和 WebStorage
對一些可有可無的數據進行緩存,方便利用。
靜態資源的請求能夠採用 CDN
,減小服務器壓力、防止沒必要要攜帶 Cookie
的場景等。
利用負載均衡的特色,開啓 Node.js 方面的 PM2 或者 Nginx 的反向代理,輪詢服務器,平均各個服務器的壓力。
返回目錄
在服務器響應的時候,咱們也能夠作 4 部分:
在發佈項目到服務器以前,咱們能夠利用一些可視化插件進行分析,使用 Happypack
等提升打包效率,項目內容上能夠作按需加載、tree shaking
等。
咱們須要熟悉瞭解 JPG/JPEG
、PNG-8/PNG-24
、GIF
、Base64
、SVG
這些圖片的特性,而後經過 Webpack 的 url-loader
將一些小圖標轉換成 Base64
,一些 Icon 使用 SVG
,一些輪播圖、Banner 圖用 JPG/JPGE
、雪碧圖的使用等。
Gzip 壓縮的原理是在一個文本文件中找一些重複出現的字符串、臨時替換它們,從而使整個文件變小(對於圖片等會處理不了)。咱們能夠經過 Webpack 的 ComparessionPlugin
進行 Gzip 壓縮,而後在 Nginx 上進行配置,就能夠利用好 Gzip 了。
服務端渲染是指瀏覽器請求的時候,服務端將已經渲染好的 HTML 頁面直接返回給瀏覽器,瀏覽器直接加載它的 HTML 渲染便可,減小了先後端交互,對 SEO 更友好。
返回目錄
瀏覽器解析渲染頁面的過程是:
DOM
樹CSS 規則樹(CSS Rule Tree)
DOM Tree
和 CSS Rule Tree
相結合,生成 渲染樹(Render Tree
)Layout of the render tree
)。Painting the render tree
)關於這個步驟咱們的優化方案有:
head
位置加載 CSS,減小 HTML 加載完畢須要等待 CSS 加載的問題。script
標籤一般放 body
後面,同時能夠利用 script
標籤的 async
和 defer
屬性,同步加載 JS 或者等 HTML 和 CSS 加載渲染完後再加載 JS。如何避免觸發迴流:
visibility
替換 display
table
佈局。對於 Render Tree
的計算一般只須要遍歷一次就能夠完成,可是 table
佈局須要計算屢次,一般要花 3 倍於等同元素的時間,所以要避免。width
、height
等會觸發迴流的操做。返回目錄
除此以外,咱們還能夠經過:
preload
預加載頁面等進行性能優化相關操做。
返回目錄
以上,咱們就經過 6 個部分,串起來說解了前端性能優化部分的知識點。
固然,確定有咱們沒有顧及的地方,歡迎小夥伴評論留言吐槽或者私聊 jsliang,jsliang 會逐步完善這塊內容。
下面咱們逐一詳細的過一下上面講到的優化知識點。
返回目錄
瀏覽器緩存能夠簡單地理解爲 HTTP
緩存。
返回目錄
瀏覽器緩存位置分 4 個部分:
Service Worker Cache
- 運行在瀏覽器背後的獨立線程。通常能夠用來實現緩存功能。Menory Cache
- 內存中的緩存。主要是頁面上已經下載的樣式、腳本、圖片等已經抓取到的資源。Disk Cache
- 硬盤中的緩存。讀取速度相對慢點。Push Cache
- 推送緩存。 是 HTTP2 中的內容,當以上 3 種緩存都沒有命中的時候,它纔會被使用。返回目錄
強緩存優先於協商緩存進行,若強制緩存生效則直接使用緩存,若不生效則進行協商緩存。強緩存不會向服務器發送請求,直接從緩存中讀取資源。
強緩存利用 HTTP 請求頭的 Expires
和 Cache-Control
兩個字段來控制。
協商緩存由服務器決定是否使用緩存,若協商緩存失效,那麼該請求的緩存失效,返回 200,從新返回資源和緩存標識,再存入瀏覽器中;生效則返回 304,繼續使用緩存。
協商緩存利用 Last-Modified + If-Modified-Since
和 Etag + If-None-Match
來實現。
具體的緩存過程小夥伴們能夠看瀏覽器緩存篇章,這裏就不哆嗦了:
返回目錄
返回目錄
Cookie
最開始被設計出來其實並非來作本地存儲的,而是爲了彌補 HTTP
在狀態管理上的不足。
Cookie
本質上就是瀏覽器裏面存儲的一個很小的文本文件,內部以鍵值對的方式來存儲。
向同一個域名下發送請求,都會攜帶相同的 Cookie
,服務器拿到 Cookie
進行解析,便能拿到客戶端的狀態。
缺陷:
4kb
,只能存儲少許信息。Cookie
請求每次都會攜帶上完整的 Cookie
,隨着請求數增多,形成性能浪費。返回目錄
Local Storge
也是針對同一個域名。
同一個域名下,會存儲相同的一段 Local Storage
。
相比 Cookie
優點:
5M
,大於 Cookie
的 4kb
。Cookie
的性能缺陷和安全缺陷。setItem
和 getItem
兩個 API 接口。應用場景:
Base64
方式存儲官方 Logo 等圖片。返回目錄
基本上和 Local Stoarge
一致。
相比較上的不一樣:
Local Storage
的持續化存儲,Session Storage
當頁面關閉的時候就不復存在了。應用場景:
返回目錄
IndexedDB
是運行在瀏覽器中的 非關係型數據庫。
由於本質上是數據庫,因此通常來講容量是沒有上線的。
返回目錄
CDN(Content Delivery Network,內容分發網絡)指的是一組分佈在各個地區的服務器。
這些服務器存儲着數據的副本,所以服務器能夠根據哪些服務器與用戶距離最近,來知足數據的請求。
CDN 提供快速服務,較少受高流量影響。
假設有一部影片出版,很是多人看。jsliang 在廣州,請求上海的服務器,結果這個服務器很是多人,資源響應地很慢。因而 jsliang 切換了路線,看到深圳服務器也有這個資源,因而向深圳服務器請求,結果能很快地看到這部影片。
在這個場景中,深圳服務器就扮演 CDN 的角色。
CDN 的核心:緩存 和 回源。
copy
一份到 CDN 服務器。應用場景:
返回目錄
若是是大型網站,負載均衡是不可或缺的內容。
PM2
:一款 Node.js 進程管理器,讓計算機每個內核都啓動一個 Node.js 服務,而且實現自動控制負載均衡。Nginx
:經過輪詢機制,將用戶的請求分配到壓力較小的服務器上(反向代理)。區別:反向代理是對服務器實現負載均衡,而 PM2
是對進程實現負載均衡。
返回目錄
Webpack 的優化瓶頸,主要是 2 個方面:
返回目錄
返回目錄
resolve.modules
用於配置 Webpack
去哪些目錄下尋找第三方模塊,默認是 ['node_modules']
,可是,它會先去當前目錄的 ./node_modules
查找,沒有的話再去 ../node_modules
,最後到根目錄。
因此能夠直接指定項目根目錄,就不須要一層一層查找。
resolve: { modules: [path.resolve(__dirname, 'node_modules')], }
返回目錄
在導入沒帶文件後綴的路徑時,Webpack
會自動帶上後綴去嘗試詢問文件是否存在,而 resolve.extensions
用於配置嘗試後綴列表;默認爲 extensions:['js', 'json']
。
當遇到 require('./data')
時 Webpack
會先嚐試尋找 data.js
,沒有再去找 data.json
;若是列表越長,或者正確的後綴越日後,嘗試的次數就會越多。
因此在配置時爲提高構建優化需遵照:
js
、jsx
、json
。返回目錄
返回目錄
以 babel-loader
爲例,能夠經過 include
和 exclude
幫助咱們避免 node_modules
這類龐大文件夾。
返回目錄
經過 ES6 的 import/export
來檢查未引用代碼,以及 sideEffects
來標記無反作用代碼,最後用 UglifyJSPlugin
來作 tree shaking
,從而刪除冗餘代碼。
返回目錄
speed-measure-webpack-plugin
:測量出在構建過程當中,每個 Loader 和 Plugin 的執行時長。webpack-bundle-analyzer
:經過矩陣樹圖的方式將包內各個模塊的大小和依賴關係呈現出來。webpack-chart
webpack-analyse
返回目錄
cache-loader
參考連接:cache-loader
在 babel-loader
開啓 cache
後,將 loader
的編譯結果寫進硬盤緩存,再次構建若是文件沒有發生變化則會直接拉取緩存。
uglifyjs-webpack-plugin
也能夠解決緩存問題。
返回目錄
Happypack
能夠將任務分解成多個子進程去併發執行,大大提高打包效率。
返回目錄
經過 DllPlugin
或者 Externals
進行靜態依賴包的分離。
因爲 CommonsChunkPlugin
每次構建會從新構建一次 vendor
,因此出於效率考慮,使用 DllPlugin
將第三方庫單獨打包到一個文件中,只有依賴自身發生版本變化時纔會從新打包。
返回目錄
由於自帶的 UglifyJsPlugin
壓縮插件是單線程運行的,而 ParallelUglifyPlugin
能夠並行執行。
因此經過 ParallelUglifyPlugin
代替自帶的 UglifyJsPlugin
插件。
返回目錄
在 Webpack
中,到底什麼是代碼分離?代碼分離容許你把代碼拆分到多個文件中。若是使用得當,你的應用性能會提升不少。由於瀏覽器能緩存你的代碼。
每當你作出一次修改,包含修改的文件須要被全部訪問你網站的人從新下載。但你並不會常常修改應用的依賴庫。
若是你能把那些依賴庫拆分到徹底分離的文件中,即便業務邏輯發生了更改,訪問者也不須要再次下載依賴庫,直接使用以前的緩存就能夠了。
因爲有了 SplitChunksPlugin
,你能夠把應用中的特定部分移至不一樣文件。若是一個模塊在不止一個 chunk
中被使用,那麼利用代碼分離,該模塊就能夠在它們之間很好地被共享。
返回目錄
UglifyJSPlugin
HtmlWebpackPlugin
splitChunks.cacheGroups
MiniCssExtractPlugin
返回目錄
經過 Code-Splitting 來作 React 的按需加載.
Code_Splitting
核心是 require-ensure
。
返回目錄
返回目錄
返回目錄
返回目錄
返回目錄
返回目錄
.svg
後綴的文件進行引用。返回目錄
img src
會發起資源請求,可是 Base64 獲得的是字符串,嵌入 HTML 中)url-loader
能夠根據文件大小來判斷是否編碼成 Base64。返回目錄
雪碧圖、CSS 精靈、CSS Sprites、圖像精靈,都是同一個玩意。
它是將小圖標和背景圖像合併到一張圖片上,而後經過 CSS 背景定位來顯示其中的每個具體部分。
它是一種優化手段,由於單張圖片所需的 HTTP 請求更少,對內存和帶寬更加友好。
返回目錄
返回目錄
經過 compression-webpack-plugin
能夠開啓 Gzip 壓縮。
若是壓縮文件過小,那不使用;可是若是具備必定規模的項目文件,能夠開啓 Gzip。
Gzip 並非萬能的,它的原理是在一個文本文件中找一些重複出現的字符串、臨時替換它們,從而使整個文件變小,因此對於圖片等會處理不了。
服務器壓縮也須要時間開銷和 CPU 開銷,因此有時候能夠用 Webpack
來進行 Gzip 壓縮,從而爲服務器分壓。
返回目錄
返回目錄
客戶端渲染中,頁面上呈現的內容,在 HTML 源文件中每每找不到。
而服務端渲染,當用戶第一次請求頁面時,服務器會把須要的組件或者頁面渲染成 HTML 字符串,返回給客戶端。
即客戶端直接拿到 HTML 內容,而不須要跑一遍 JS 去生成 DOM 內容。
「所見即所得」,服務端渲染情景下,頁面上呈現的內容,在 HTML 源文件裏面也能夠找到。
返回目錄
假設 A 網站關鍵字上有 前端性能優化,可是這篇文章只有 A 網站服務器搜索事後纔會出來結果,這時候搜索引擎是沒法找到的。
爲了更好的 SEO 效果,就要拿 「現成的內容」 給搜索引擎看,就要開啓服務端渲染。
其次,服務端渲染解決了一個性能問題 —— 首屏加載速度過慢。
從輸入 URL 到頁面渲染過程當中咱們知道,若是是客戶端渲染,咱們須要加載 HTML、CSS,而後再通過 JS 造成 Render Tree
,定位後再繪製頁面。
這個過程當中用戶一直在等待,若是採用了服務端渲染,那麼服務端能夠直接給一個能夠拿來呈現給用戶的頁面。
返回目錄
給 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,就不哆嗦了。
返回目錄
SSR 主要用於解決單頁應用首屏渲染慢以及 SEO 問題,同時也解決了與後端同窗的溝通成本。但同時:提升了服務器壓力,吃 CPU,內存等資源,優化很差提升成本。
返回目錄
瀏覽器內核決定了瀏覽器解釋網頁語法的方式。
目前常見的瀏覽器內核有:Trident
(IE)、Gecko
(火狐)、Blink
(Chrome、Opera)、Webkit
(Safari)。
返回目錄
如上圖,瀏覽器的渲染過程爲:
DOM
樹CSS 規則樹(CSS Rule Tree)
DOM Tree
和 CSS Rule Tree
相結合,生成 渲染樹(Render Tree
)Layout of the render tree
)。Painting the render tree
)返回目錄
咱們正常的閱讀順序是從左往右的,可是 CSS 解析器解析 CSS 的時候,採用的是古人的規則。
#ul li {}
這樣的一行規則,咱們寫起來的時候很順暢:先找 id
爲 ul
的元素,再找裏面的 li
元素。
可是實際上 CSS 解析器是從右往左的,它會先查找全部 li
元素,而且逐個確認這個 li
元素的父元素的 id
是否是 ul
,這就坑死了。
因此像通配符 * { padding: 0; margin: 0 }
這種,小夥伴們就應該減小設置,要否則頁面的元素越多遍歷匹配越久。
總結一下:
*
等。span
替換爲 .span
。#ul li a
。返回目錄
爲了不 HTML 解析完畢,可是 CSS 沒有解析完畢,從而致使頁面直接 「裸奔」 在用戶面前的問題,瀏覽器在處理 CSS 規則樹的時候,不會渲染任何已處理的內容。
因此不少時候,咱們會讓網頁儘早處理 CSS,即在 head
標籤中啓用 link
或者啓用 CDN 實現靜態資源加載速度的優化。
返回目錄
在上面的加載過程當中咱們並無提到 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
。
返回目錄
當使用 JS 去操做 DOM 的時候,其實是 JS 引擎和渲染引擎之間的溝通,這個溝通的過程要開銷的。
每操做一次 DOM 就收費一次,多了頁面就卡起來咯。
同時,操做 DOM 的時候修改了尺寸等元素,還會引發迴流和重繪。
layout
)。當元素的尺寸、結構或者觸發某些屬性時,瀏覽器會從新渲染頁面,稱爲迴流。此時,瀏覽器須要從新通過計算,計算後還須要從新頁面佈局,所以是較重的操做。什麼操做觸發迴流?
border
、margin
、padding
、width
、height
)resize
)什麼操做觸發重繪?
background
、color
)visibility
)background-image
)
咱們仔細看這張圖,能夠看到重排(Layout
)會致使 Render Tree
重構,進而觸發重繪(Painting
):
所以,咱們操做 DOM 的時候,能夠這麼優化:
visibility
替換 display
table
佈局。對於 Render Tree
的計算一般只須要遍歷一次就能夠完成,可是 table
佈局須要計算屢次,一般要花 3 倍於等同元素的時間,所以要避免。width
、height
等會觸發迴流的操做。返回目錄
preload
提供了一種聲明式的命令,讓瀏覽器提早加載指定資源(加載後並不執行),在須要執行的時候再執行。
提供的好處主要是:
document
的 onload
事件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
標籤。
區分 preload
和 prefetch
:
preload
:告訴瀏覽器頁面一定須要的資源,瀏覽器必定會加載這些資源。prefetch
:告訴瀏覽器頁面可能須要的資源,瀏覽器不必定會加載這些資源。固然,開發中須要注意:
preload
preload
和 prefetch
preload
加載跨域資源返回目錄
返回目錄
懶加載實現思路:
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
返回目錄
無限滾動在移動端很常見,可是可見區域渲染並不常見,主要是由於 IOS 上 UIWebView 的 onscroll
並不能實時觸發。
實現可見區域渲染的思路:
startIndex
endIndex
startIndex
對應的數據在整個列表中的偏移位置 startOffset
,並設置到列表上
代碼實現:略。
返回目錄
Performance
Page Speed
自動化工具 Lighthouse
npm i lighthouse -g
、lighthouse https://www.baidu.com
LightHouse
的 Audits
面板返回目錄
本篇參考文獻有 31 篇。
返回目錄
2019 年文章:
2018 年文章:
2017 年文章:
返回目錄
jsliang 的文檔庫由 梁峻榮 採用 知識共享 署名-非商業性使用-相同方式共享 4.0 國際 許可協議 進行許可。<br/>基於 https://github.com/LiangJunrong/document-library 上的做品創做。<br/>本許可協議受權以外的使用權限能夠從 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 處得到。