
1.重要性
當我們面試的時候,前端性能優化方面算是必考的知識點,但是工作中我們又很少會重點的對項目進行前端優化,它真的不重要嗎?
如果我們可以將後端響應時間縮短一半,整體響應時間只能減少5%~10%。而如果關注前端性能,同樣是將其響應時間減少一半,則整體響應時間可以減少40%~45%。
改進前端通常只需要較少的時間和資源,減少後端延遲會帶來很大的改動。
只有10%~20%的最終用戶響應時間花在了下載HTML文檔上,其餘的80%~90%時間花在了下載頁面中的所有組件上。
2.定位
2.1 技術上的選擇
在前端日常開發中,技術上的選擇是非常重要的。為什麼要講這個呢?因為現象頻發。
前端工程化嚴重的當下,輕量化的框架慢慢被遺忘掉了。並不是所有的業務場景都適合使用工程化框架,react/vue 並不輕量。
複雜的框架是為了解決複雜的業務
如果研發h5、PC展示等場景簡單的業務時候,javascript原生 配合一些輕量化插件更適合。
多頁面應用也並不都是缺點。根據業務不同而選擇不一樣的技術是非常重要的,是每個前端都應該反思的事情。
這方面是導致卡頓的關鍵問題。
2.2 NetWork
我們的老朋友NetWork想必前端同學都很熟悉。我們先來看一下network

金融數字宏觀Financial numbers macro
從network面板上我們可以看出一些信息:
- 請求資源size
- 請求資源時長
- 請求資源數量
- 介面響應時長
- 介面發起數量
- 介面報文size
- 介面響應狀態
- 瀑布圖
瀑布圖是什麼呢?
瀑布圖就是上方圖片後面的waterfall縱列
瀑布圖是一個級聯圖, 展示了瀏覽器如何載入資源並渲染成網頁. 圖中的每一行都是一次單獨的瀏覽器請求. 這個圖越長, 說明載入網頁過程中所發的請求越多. 每一行的寬度, 代表瀏覽器發出請求並下載該資源的過程中所耗費的時間。它的側重點在於分析網路鏈路
瀑布圖顏色說明:
- DNS Lookup [深綠色] – 在瀏覽器和伺服器進行通信之前, 必須經過DNS查詢, 將域名轉換成IP地址. 在這個階段, 你可以處理的東西很少. 但幸運的是, 並非所有的請求都需要經過這一階段.
- Initial Connection [橙色] – 在瀏覽器發送請求之前, 必須建立TCP連接. 這個過程僅僅發生在瀑布圖中的開頭幾行, 否則這就是個性能問題(後邊細說).
- SSL/TLS Negotiation [紫色] – 如果你的頁面是通過SSL/TLS這類安全協議載入資源, 這段時間就是瀏覽器建立安全連接的過程. 目前Google將HTTPS作為其 搜索排名因素 之一, SSL/TLS 協商的使用變得越來越普遍了.
- Time To First Byte (TTFB) [綠色] – TTFB 是瀏覽器請求發送到伺服器的時間+伺服器處理請求時間+響應報文的第一位元組到達瀏覽器的時間. 我們用這個指標來判斷你的web伺服器是否性能不夠, 或者說你是否需要使用CDN.
- Downloading (藍色) – 這是瀏覽器用來下載資源所用的時間. 這段時間越長, 說明資源越大. 理想情況下, 你可以通過控制資源的大小來控制這段時間的長度.
那麼除了瀑布圖的長度外,我們如何才能判斷一個瀑布圖的狀態是健康的呢?
- 首先, 減少所有資源的載入時間. 亦即減小瀑布圖的寬度. 瀑布圖越窄, 網站的訪問速度越快.
- 其次, 減少請求數量 也就是降低瀑布圖的高度. 瀑布圖越矮越好.
- 最後, 通過優化資源請求順序來加快渲染時間. 從圖上看, 就是將綠色的”開始渲染”線向左移. 這條線向左移動的越遠越好.
這樣,我們就可以從network的角度去排查「慢」的問題。
2.3 webpack-bundle-analyzer
項目構建後生成的bundle包是壓縮後的。webpack-bundle-analyzer是一款包分析工具。
我們先來看一下它能帶來的效果。如下圖:

從上圖來看,我們的bundle包被解析的一覽無餘。其中模塊面積占的越大說明在bundle包中size越大。就值得注意了,重點優化一下。
它能夠排查出來的信息有
- 顯示包中所有打入的模塊
- 顯示模塊size 及 gzip後的size
排查包中的模塊情形是非常有必要的,通過webpack-bundle-analyzer來排查出一些無用的模塊,過大的模塊。然後進行優化。以減少我們的bundle包size,減少載入時長。
安裝
# NPM
npm install --save-dev webpack-bundle-analyzer
# Yarn
yarn add -D webpack-bundle-analyzer
複製代碼
複製代碼使用(as a Webpack-Plugin)
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
複製代碼
複製代碼然後構建包完畢後會自動彈出一個窗口展示上圖信息。
2.4 Performance
chrome自帶的performance模塊。先附上一個官網文檔傳送門:Performance
可以檢測很多方面的數據,多數情況的性能排查上用的比較多。如果想要深入了解的同學建議去看一下官方文檔。
接下來我們來說一下在performance面板中如何排查「慢」的問題,它給我們提供了哪些信息呢。先附上一張performance的面板圖片。

從上圖中可以分析出一些指標
- FCP/LCP 時間是否過長?
- 請求並發情況 是否並發頻繁?
- 請求發起順序 請求發起順序是否不對?
- javascript執行情況 javascript執行是否過慢?
這些指標就是我們需要重點關注的,當然performance的功能並不止於此。
先記住如何獲取到這些指標,後面來一一進行解析優化。
2.5 PerformanceNavigationTiming
獲取各個階段的響應時間,我們所要用到的介面是PerformanceNavigationTiming介面。
PerformanceNavigationTiming 提供了用於存儲和檢索有關瀏覽器文檔事件的指標的方法和屬性。 例如,此介面可用於確定載入或卸載文檔需要多少時間。
function showNavigationDetails() {
const [entry] = performance.getEntriesByType("navigation");
console.table(entry.toJSON());
}
複製代碼使用這個函數,我們就可以獲取各個階段的響應時間,如圖:

參數說明
navigationStart 載入起始時間
redirectStart 重定向開始時間(如果發生了HTTP重定向,每次重定向都和當前文檔同域的話,就返回開始重定向的fetchStart的值。其他情況,則返回0)
redirectEnd 重定向結束時間(如果發生了HTTP重定向,每次重定向都和當前文檔同域的話,就返回最後一次重定向接收完數據的時間。其他情況則返回0)
fetchStart 瀏覽器發起資源請求時,如果有緩存,則返回讀取緩存的開始時間
domainLookupStart 查詢DNS的開始時間。如果請求沒有發起DNS請求,如keep-alive,緩存等,則返回fetchStart
domainLookupEnd 查詢DNS的結束時間。如果沒有發起DNS請求,同上
connectStart 開始建立TCP請求的時間。如果請求是keep-alive,緩存等,則返回domainLookupEnd
(secureConnectionStart) 如果在進行TLS或SSL,則返回握手時間
connectEnd 完成TCP鏈接的時間。如果是keep-alive,緩存等,同connectStart
requestStart 發起請求的時間
responseStart 伺服器開始響應的時間
domLoading 從圖中看是開始渲染dom的時間,具體未知
domInteractive 未知
domContentLoadedEventStart 開始觸發DomContentLoadedEvent事件的時間
domContentLoadedEventEnd DomContentLoadedEvent事件結束的時間
domComplete 從圖中看是dom渲染完成時間,具體未知
loadEventStart 觸發load的時間,如沒有則返回0
loadEventEnd load事件執行完的時間,如沒有則返回0
unloadEventStart unload事件觸發的時間
unloadEventEnd unload事件執行完的時間
關於我們的Web性能,我們會用到的時間參數:
DNS解析時間: domainLookupEnd – domainLookupStart
TCP建立連接時間: connectEnd – connectStart
白屏時間: responseStart – navigationStart
dom渲染完成時間: domContentLoadedEventEnd – navigationStart
頁面onload時間: loadEventEnd – navigationStart
根據這些時間參數,我們就可以判斷哪一階段對性能有影響。
2.6 抓包
有一些業務狀況是沒有上述的一些調試工具該怎麼辦呢?我們可以利用抓包工具進行對頁面信息對抓取,上述我們通過chrome工具排查出來的指標,也可以通過抓包工具進行抓取。
這裡我推薦一款抓包工具charles。
2.7 性能測試工具
2.7.1 Pingdom
2.7.2 Load Impact
2.7.3 WebPage Test
2.7.4 Octa Gate Site Timer
2.7.5 Free Speed Test
3.優化
前端的優化種類繁多,主要包含三個方面的優化:網路優化(對載入時所消耗的網路資源優化),代碼優化(資源載入完後,腳本解釋執行的速度),框架優化(選擇性能較好的框架,比如benchmark)。
3.1 tree shaking
中文(搖樹),webpack構建優化中重要一環。搖樹用於清除我們項目中的一些無用代碼,它依賴於ES中的模塊語法。
比如日常使用lodash的時候
import _ from 'lodash'
複製代碼
複製代碼如果如上引用lodash庫,在構建包的時候是會把整個lodash包打入到我們的bundle包中的。
import _isEmpty from 'lodash/isEmpty';
複製代碼
複製代碼如果如上引用lodash庫,在構建包的時候只會把isEmpty這個方法抽離出來再打入到我們的bundle包中。
這樣的化就會大大減少我們包的size。所以在日常引用第三方庫的時候,需要注意導入的方式。
如何開啟搖樹
在webpack4.x 中默認對tree-shaking進行了支持。 在webpack2.x 中使用tree-shaking:傳送門
3.2 split chunks
中文(分包)
在沒配置任何東西的情況下,webpack 4 就智能的幫你做了代碼分包。入口文件依賴的文件都被打包進了main.js,那些大於 30kb 的第三方包,如:echarts、xlsx、dropzone等都被單獨打包成了一個個獨立 bundle。
其它被我們設置了非同步載入的頁面或者組件變成了一個個chunk,也就是被打包成獨立的bundle。
它內置的代碼分割策略是這樣的:
- 新的 chunk 是否被共享或者是來自 node_modules 的模塊
- 新的 chunk 體積在壓縮之前是否大於 30kb
- 按需載入 chunk 的並發請求數量小於等於 5 個
- 頁面初始載入時的並發請求數量小於等於 3 個
大家可以根據自己的項目環境來更改配置。配置代碼如下:
splitChunks({
cacheGroups: {
vendors: {
name: `chunk-vendors`,
test: /[\/]node_modules[\/]/,
priority: -10,
chunks: 'initial',
},
dll: {
name: `chunk-dll`,
test: /[\/]bizcharts|[\/]@antv[\/]data-set/,
priority: 15,
chunks: 'all',
reuseExistingChunk: true
},
common: {
name: `chunk-common`,
minChunks: 2,
priority: -20,
chunks: 'all',
reuseExistingChunk: true
},
}
})
複製代碼
複製代碼沒有使用webpack4.x版本的項目,依然可以通過按需載入的形式進行分包,使得我們的包分散開,提升載入性能。
按需載入也是以前分包的重要手段之一
這裡推薦一篇非常好的文章:webpack如何使用按需載入
3.3 拆包
與3.2的分包不同。大家可能沒發現,上面2.3的bundle包解析中有個有趣的現象,上面項目的技術棧是react,但是bundle包中並沒有react、react-dom、react-router等。
因為把這些插件「拆」開了。並沒有一起打在bundle中。而是放在了CDN上。下面我舉一個例子來解釋一下。
假設:原本bundle包為2M,一次請求拉取。拆分為 bundle(1M) + react桶(CDN)(1M) 兩次請求並發拉取。
從這個角度來看,1+1的模式拉取資源更快。
換一個角度來說,全量部署項目的情況,每次部署bundle包都將重新拉取。比較浪費資源。react桶的方式可以命中強緩存,這樣的化,就算全量部署也只需要重新拉取左側1M的bundle包即可,節省了伺服器資源。優化了載入速度。
注意:在本地開發過程中,react等資源建議不要引入CDN,開發過程中刷新頻繁,會增加CDN伺服器壓力,走本地就好。
3.4 gzip
服務端配置gzip壓縮後可大大縮減資源大小。
Nginx配置方式
http {
gzip on;
gzip_buffers 32 4K;
gzip_comp_level 6;
gzip_min_length 100;
gzip_types application/javascript text/css text/xml;
gzip_disable "MSIE [1-6].";
gzip_vary on;
}
複製代碼
複製代碼配置完成後在response header中可以查看。

3.5 圖片壓縮
開發中比較重要的一個環節,我司自己的圖床工具是自帶壓縮功能的,壓縮後直接上傳到CDN上。
如果公司沒有圖床工具,我們該如何壓縮圖片呢?我推薦幾種我常用的方式
- 智圖壓縮 (百度很難搜到官網了,免費、批量、好用)
- tinypng(免費、批量、速度快)
- fireworks工具壓縮像素點和尺寸 (自己動手,掌握尺度)
- 找UI壓縮後發給你
圖片壓縮是常用的手法,因為設備像素點的關係,UI給予的圖片一般都是 x2,x4的,所以壓縮就非常有必要。
3.6 圖片分割
如果頁面中有一張效果圖,比如真機渲染圖,UI手拿著刀不讓你壓縮。這時候不妨考慮一下分割圖片。
建議單張圖圖片的大小不要超過100k,我們在分割完圖片後,通過布局再拼接在一起。可以圖片載入效率。
這裡注意一點,分割後的每張圖片一定要給height,否則網速慢的情況下樣式會塌陷。
3.7 sprite
南方叫精靈圖,北方叫雪碧圖。這個現象就很有趣。
在網站中有很多小圖片的時候,一定要把這些小圖片合併為一張大的圖片,然後通過background分割到需要展示的圖片。
這樣的好處是什麼呢?先來普及一個規則
瀏覽器請求資源的時候,同源域名請求資源的時候有最大並發限制,chrome為6個,就比如你的頁面上有10個相同CDN域名小圖片,那麼需要發起10次請求去拉取,分兩次並發。第一次並發請求回來後,發起第二次並發。
如果你把10個小圖片合併為一張大圖片的畫,那麼只用一次請求即可拉取下來10個小圖片的資源。減少伺服器壓力,減少並發,減少請求次數。
附上一個sprite的例子。

3.8 CDN
中文(內容分發網路),伺服器是中心化的,CDN是「去中心化的」。
在項目中有很多東西都是放在CDN上的,比如:靜態文件,音頻,視頻,js資源,圖片。那麼為什麼用CDN會讓資源載入變快呢?
舉個簡單的例子:
以前買火車票大家都只能去火車站買,後來我們買火車票就可以在樓下的火車票代售點買了。
你細品。
所以靜態資源度建議放在CDN上,可以加快資源載入的速度。
3.9 懶載入
懶載入也叫延遲載入,指的是在長網頁中延遲載入圖像,是一種非常好的優化網頁性能的方式。
當可視區域沒有滾到資源需要載入的地方時候,可視區域外的資源就不會載入。
可以減少伺服器負載,常適用於圖片很多,頁面較長的業務場景中。
如何使用懶載入呢?
- 圖片懶載入
- layzr.js
3.10 iconfont
中文(字體圖表),現在比較流行的一種用法。使用字體圖表有幾種好處
- 矢量
- 輕量
- 易修改
- 不佔用圖片資源請求。
就像上面說的雪碧圖,如果都用字體圖標來替換的畫,一次請求都免了,可以直接打到bundle包中。
使用前提是UI給點力,設計趨向於字體圖標,提前給好資源,建立好字體圖標庫。
3.11 邏輯後移
邏輯後移是一種比較常見的優化手段。用一個打開文章網站的操作來舉個例子。
沒有邏輯後移處理的請求順序是這個樣子的

頁面的展示主體是文章展示,如果文章展示的請求靠後了,那麼渲染文章出來的時間必然靠後,因為有可能因為請求阻塞等情況,影響請求響應情況,如果超過一次並發的情況的話,會更加的慢。如圖的這種情況也是在我們項目中發生過的。
很明顯我們應該把主體「請求文章」介面前移,把一些非主體的請求邏輯後移。這樣的話可以儘快的把主體渲染出來,就會快很多。
優化後的順序是這個樣子的。

在平常的開發中建議時常注意邏輯後移的情況,突出主體邏輯。可以極大的提升用戶體驗。
3.12 演算法複雜度
在數據量大的應用場景中,需要著重注意演算法複雜度問題。
在這個方面可以參考Javascript演算法之複雜度分析這篇文章。
如上面Performance解析出的Javascript執行指標上,可以推測出來你的code執行效率如何,如果執行時間過長就要考慮一下是否要優化一下複雜度了。
在時間換空間,空間換時間的選擇上,要根據業務場景來進行取捨。
3.13 組件渲染
拿react舉例,組件分割方面不要太深。需要控制組件的渲染,尤其是深層組件的render。
老生常談的話題,我們可以一些方式來優化組件渲染
- 生命周期控制 – 比如react的shouldComponentUpdate來控制組件渲染。
- 官網提供的api- PureComponent
- 控制注入組件的參數
- 分配組件唯一key
沒有必要的渲染是對性能的極大浪費。
3.14 node middleware
中文(node 中間件)
中間件主要是指封裝所有Http請求細節處理的方法。一次Http請求通常包含很多工作,如記錄日誌、ip過濾、查詢字元串、請求體解析、Cookie處理、許可權驗證、參數驗證、異常處理等,但對於Web應用而言,並不希望接觸到這麼多細節性的處理,因此引入中間件來簡化和隔離這些基礎設施與業務邏輯之間的細節,讓我們能夠關注在業務的開發上,以達到提升開發效率的目的。
使用node middleware合併請求。減少請求次數。這種方式也是非常實用的。
3.15 web worker
Web Worker 的作用,就是為 JavaScript 創造多線程環境,允許主線程創建 Worker 線程,將一些任務分配給後者運行。在主線程運行的同時,Worker 線程在後台運行,兩者互不干擾。等到 Worker 線程完成計算任務,再把結果返回給主線程。這樣的好處是,一些計算密集型或高延遲的任務,被 Worker 線程負擔了,主線程(通常負責 UI 交互)就會很流暢,不會被阻塞或拖慢。
合理使用web worker可以優化複雜計算任務。這裡直接拋阮一峰的入門文章:傳送門
3.16 緩存
緩存的原理就是更快讀寫的存儲介質+減少IO+減少CPU計算=性能優化。而性能優化的第一定律就是:優先考慮使用緩存。
緩存的主要手段有:瀏覽器緩存、CDN、反向代理、本地緩存、分散式緩存、資料庫緩存。
3.17 GPU渲染
每個網頁或多或少都涉及到一些CSS動畫,通常簡單的動畫對於性能的影響微乎其微,然而如果涉及到稍顯複雜的動畫,不當的處理方式會使性能問題變得十分突出。
像Chrome, FireFox, Safari, IE9+和最新版本的Opera都支持GPU加速,當它們檢測到頁面中某個DOM元素應用了某些CSS規則時就會開啟。
雖然我們可能不想對元素應用3D變換,可我們一樣可以開啟3D引擎。例如我們可以用transform: translateZ(0) 來開啟GPU加速 。
只對我們需要實現動畫效果的元素應用以上方法,如果僅僅為了開啟硬體加速而隨便亂用,那是不合理的。
3.18 Ajax可緩存
Ajax在發送的數據成功後,為了提高頁面的響應速度和用戶體驗,會把請求的URL和返回的響應結果保存在緩存內,當下一次調用Ajax發送相同的請求(URL和參數完全相同)時,它就會直接從緩存中拿數據。
在進行Ajax請求的時候,可以選擇盡量使用get方法,這樣可以使用客戶端的緩存,提高請求速度。
3.19 Resource Hints
Resource Hints(資源預載入)是非常好的一種性能優化方法,可以大大降低頁面載入時間,給用戶更加流暢的用戶體驗。
現代瀏覽器使用大量預測優化技術來預測用戶行為和意圖,這些技術有預連接、資源與獲取、資源預渲染等。
Resource Hints 的思路有如下兩個:
- 當前將要獲取資源的列表
- 通過當前頁面或應用的狀態、用戶歷史行為或 session 預測用戶行為及必需的資源
實現Resource Hints的方法有很多種,可分為基於 link 標籤的 DNS-prefetch、subresource、preload、 prefetch、preconnect、prerender,和本地存儲 localStorage。
3.20 SSR
渲染過程在伺服器端完成,最終的渲染結果 HTML 頁面通過 HTTP 協議發送給客戶端,又被認為是『同構’或『通用’,如果你的項目有大量的detail頁面,相互特別頻繁,建議選擇服務端渲染。
服務端渲染(SSR)除了SEO還有很多時候用作首屏優化,加快首屏速度,提高用戶體驗。但是對伺服器有要求,網路傳輸數據量大,佔用部分伺服器運算資源。
Vue的Nuxt.js和React的next.js都是服務端渲染的方法。
3.21 UNPKG
UNPKG是一個提供npm包進行CDN加速的站點,因此,可以將一些比較固定了依賴寫入html模版中,從而提高網頁的性能。首先,需要將這些依賴聲明為external,以便webpack打包時不從node_modules中載入這些資源,配置如下:
externals: { 'react': 'React' }
複製代碼其次,你需要將所依賴的資源寫在html模版中,這一步需要用到html-webpack-plugin。下面是一段示例:
<% if (htmlWebpackPlugin.options.node_env === 'development') { %>
<script src="https://unpkg.com/react@16.7.0/umd/react.development.js"></script>
<% } else { %>
<script src="https://unpkg.com/react@16.7.0/umd/react.production.min.js"></script>
<% } %>
複製代碼這段代碼需要注入node_env,以便在開發的時候能夠獲得更友好的錯誤提示。也可以選擇一些比較自動的庫,來幫助我們完成這個過程,比如webpack-cdn-plugin,或者dynamic-cdn-webpack-plugin。
4.總結
還有一些比較常用的優化方法我沒有列舉出來,例如將樣式表放在頂部,將腳本放在底部,減少重繪,按需載入,模塊化等。方法很多,對症下藥才是關鍵
原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/281364.html
微信掃一掃
支付寶掃一掃