哈嘍, 今天給大家分享:前端性能優化——首頁資源壓縮 63%、白屏時間縮短 86%。
提升首屏的加載速度,是前端性能優化中「最重要」的環節,這里筆者梳理出一些常規且有效的首屏優化建議
「目標:」通過對比優化前后的性能變化,來驗證方案的有效性,了解并掌握其原理
1、路由懶加載
SPA 項目,一個路由對應一個頁面,如果不做處理,項目打包后,會把所有頁面打包成一個文件,「當用戶打開首頁時,會一次性加載所有的資源」,造成首頁加載很慢,降低用戶體驗
列一個實際項目的打包詳情:
將路由全部改成懶加載
//?通過webpackChunkName設置分割后代碼塊的名字
const?Home?=?()?=>?import(/*?webpackChunkName:?"home"?*/?"@/views/home/index.vue");
const?MetricGroup?=?()?=>?import(/*?webpackChunkName:?"metricGroup"?*/?"@/views/metricGroup/index.vue");
…………
const?routes?=?[
????{
???????path:?"/",
???????name:?"home",
???????component:?Home
????},
????{
???????path:?"/metricGroup",
???????name:?"metricGroup",
???????component:?MetricGroup
????},
????…………
?]
重新打包后,首頁資源拆分為 app.js 和 home.js,以及對應的 css 文件
通過路由懶加載,該項目的首頁資源壓縮約52%
路由懶加載的原理
懶加載前提的實現:ES6 的動態地加載模塊——import()
調用 import() 之處,被作為分離的模塊起點,意思是,被請求的模塊和它引用的所有子模塊,會分離到一個單獨的 chunk 中
——摘自《webpack——模塊方法》的 import\(\)小節
要實現懶加載,就得先將進行懶加載的子模塊分離出來,打包成一個單獨的文件
作用是 webpack 在打包的時候,對異步引入的庫代碼(lodash)進行代碼分割時,設置代碼塊的名字。webpack 會將任何一個異步模塊與相同的塊名稱組合到相同的異步塊中
2、組件懶加載
除了路由的懶加載外,組件的懶加載在很多場景下也有重要的作用
舉個 :
home 頁面 和 about 頁面,都引入了 彈框組件,該彈框不是一進入頁面就加載,而是需要用戶手動觸發后才展示出來
home 頁面示例:
??<div?class="homeView">
????<p>home?頁面p>
????<el-button?@click="dialogVisible?=?!dialogVisible">打開彈框el-button>
????<dialogInfo?v-if="dialogVisible"?/>
??div>
</template>
<script>
import?dialogInfo?from?'@/components/dialogInfo';
export?default?{
??name:?'homeView',
??components:?{
????dialogInfo
??}
}
</script>
項目打包后,發現 home.js 和 about.js 均包括了該彈框組件的代碼(在 dist 文件中搜索 彈框組件)
當用戶打開 home 頁時,會一次性加載該頁面所有的資源,「我們期望的是用戶觸發按鈕后,再加載該彈框組件的資源」
這種場景下,就很適合用懶加載的方式引入
彈框組件懶加載:
<script>
const?dialogInfo?=?()?=>?import(/*?webpackChunkName:?"dialogInfo"?*/?'@/components/dialogInfo');
export?default?{
??name:?'homeView',
??components:?{
????dialogInfo
??}
}
script>
重新打包后,home.js 和 about.js 中沒有了彈框組件的代碼,該組件被獨立打包成 .js,「當用戶點擊按鈕時,才會去加載 .js 和 .css」
最終,使用組件路由懶后,該項目的首頁資源進一步減少約11%
組件懶加載的使用場景
有時資源拆分的過細也不好,可能會造成瀏覽器 http 請求的增多
總結出三種適合組件懶加載的場景:
1)該頁面的 JS 文件體積大,導致頁面打開慢,可以通過組件懶加載進行資源拆分,利用瀏覽器并行下載資源,提升下載速度(比如首頁)
2)該組件不是一進入頁面就展示,需要一定條件下才觸發(比如彈框組件)
3)該組件復用性高,很多頁面都有引入,利用組件懶加載抽離出該組件,一方面可以很好利用緩存,同時也可以減少頁面的 JS 文件大小(比如表格組件、圖形組件等)
3、合理使用 Tree shaking
Tree shaking 的作用:消除無用的 JS 代碼,減少代碼體積
舉個 :
//?util.js
export?function?targetType(target)?{
??return?Object.prototype.toString.call(target).slice(8,?-1).toLowerCase();
}
export?function?deepClone(target)?{
??return?JSON.parse(JSON.stringify(target));
}
項目中只使用了 方法,但未使用 方法,項目打包后, 方法不會被打包到項目里
tree-shaking 原理:
依賴于 ES6 的模塊特性,ES6 模塊依賴關系是確定的,和運行時的狀態無關,可以進行可靠的靜態分析,這就是 tree-shaking 的基礎
靜態分析就是不需要執行代碼,就可以從字面量上對代碼進行分析。ES6 之前的模塊化,比如 是動態加載,只有執行后才知道引用的什么模塊,就不能通過靜態分析去做優化,正是基于這個基礎上,才使得 tree-shaking 成為可能
Tree shaking 并不是萬能的
并不是說所有無用的代碼都可以被消除,還是上面的代碼,換個寫法 tree-shaking 就失效了
//?util.js
export?default?{
??targetType(target)?{
????return?Object.prototype.toString.call(target).slice(8,?-1).toLowerCase();
??},
??deepClone(target)?{
????return?JSON.parse(JSON.stringify(target));
??}
};
//?引入并使用
import?util?from?'../util';
util.targetType(null)
同樣的,項目中只使用了 方法,未使用 方法,項目打包后, 方法還是被打包到項目里
在 dist 文件中搜索 方法:
究其原因,export default 導出的是一個對象,「無法通過靜態分析判斷出一個對象的哪些變量未被使用,所以 tree-shaking 只對使用 export 導出的變量生效」
這也是函數式編程越來越火的原因,因為可以很好利用 tree-shaking 精簡項目的體積,也是 vue3 全面擁抱了函數式編程的原因之一
4、骨架屏優化白屏時長
使用骨架屏,可以縮短白屏時間,提升用戶體驗。國內大多數的主流網站都使用了骨架屏,特別是手機端的項目
SPA 單頁應用,無論 vue 還是 react,最初的 html 都是空白的,需要通過加載 JS 將內容掛載到根節點上,這套機制的副作用:會造成長時間的白屏
常見的骨架屏插件就是基于這種原理,在項目打包時將骨架屏的內容直接放到 html 文件的根節點中
使用骨架屏插件,打包后的 html 文件(根節點內部為骨架屏):
同一項目,對比使用骨架屏前后的 FP 白屏時間:
骨架屏確實是優化白屏的不二選擇,白屏時間縮短了86%
骨架屏插件
這里以vue--webpack-plugin插件為例,該插件的亮點是可以給不同的頁面設置不同的骨架屏,這點確實很酷
1)安裝
npm?i?vue-skeleton-webpack-plugin
2)vue.config.js 配置
//?骨架屏
const?SkeletonWebpackPlugin?=?require("vue-skeleton-webpack-plugin");
module.exports?=?{
???configureWebpack:?{
??????plugins:?[
???????new?SkeletonWebpackPlugin({
????????//?實例化插件對象
????????webpackConfig:?{
??????????entry:?{
????????????app:?path.join(__dirname,?'./src/skeleton.js')?//?引入骨架屏入口文件
??????????}
????????},
????????minimize:?true,?//?SPA?下是否需要壓縮注入?HTML?的?JS?代碼
????????quiet:?true,?//?在服務端渲染時是否需要輸出信息到控制臺
????????router:?{
??????????mode:?'hash',?//?路由模式
??????????routes:?[
????????????//?不同頁面可以配置不同骨架屏
????????????//?對應路徑所需要的骨架屏組件id,id的定義在入口文件內
????????????{?path:?/^\/home(?:\/)?/i,?skeletonId:?'homeSkeleton'?},
????????????{?path:?/^\/detail(?:\/)?/i,?skeletonId:?'detailSkeleton'?}
??????????]
????????}
??????})
??????]
???}
}
3)新建 .js 入口文件
// skeleton.js
import Vue from "vue";
// 引入對應的骨架屏頁面
import homeSkeleton from "./views/homeSkeleton";
import detailSkeleton from "./views/detailSkeleton";
export default new Vue({
components: {
homeSkeleton,
detailSkeleton,
},
template: `
`,
});
5、長列表虛擬滾動
首頁中不乏有需要渲染長列表的場景,當渲染條數過多時,所需要的渲染時間會很長,滾動時還會造成頁面卡頓,整體體驗非常不好
「虛擬滾動——指的是只渲染可視區域的列表項,非可見區域的」不渲染,在滾動時動態更新可視區域,該方案在優化大量數據渲染時效果是很明顯的
虛擬滾動圖例:
虛擬滾動基本原理:
計算出 列表總高度,并在觸發時滾動事件時根據 值不斷更新 以及 ,以此從列表數據 中截取對應元素
虛擬滾動性能對比:
使用虛擬滾動使性能提升了78%
虛擬滾動插件
虛擬滾動的插件有很多,比如 vue-virtual-、vue-virtual-scroll-list、react-tiny-virtual-list、react- 等
這里簡單介紹vue-virtual-的使用
//?安裝插件
npm?install?vue-virtual-scroller
//?main.js
import?VueVirtualScroller?from?'vue-virtual-scroller'
import?'vue-virtual-scroller/dist/vue-virtual-scroller.css'
Vue.use(VueVirtualScroller)
//?使用
??<RecycleScroller
????class="scroller"
????:items="list"
????:item-size="32"
????key-field="id"
????v-slot="{?item?}">
??????<div?class="user">?{{?item.name?}}?div>
??RecycleScroller>
</template>
該插件主要有 .vue、.vue 這兩個組件,其中 需要 item 的高度為靜態的,也就是列表每個 item 的高度都是一致的,而 可以兼容 item 的高度為動態的情況
6、Web Worker 優化長任務
由于瀏覽器 GUI 渲染線程與 JS 引擎線程是互斥的關系,當頁面中有很多長任務時,會造成頁面 UI 阻塞,出現界面卡頓、掉幀等情況
查看頁面的長任務:
打開控制臺,選擇 工具,點擊 Start 按鈕,展開 Main 選項,會發現有很多紅色的三角,這些就屬于長任務(長任務:執行時間超過 50ms 的任務)
測試實驗:
如果直接把下面這段代碼直接丟到主線程中,計算過程中頁面一直處于卡死狀態,無法操作
let?sum?=?0;
for?(let?i?=?0;?i?200000;?i++)?{
????for?(let?i?=?0;?i?10000;?i++)?{
??????sum?+=?Math.random()
????}
??}
使用 Web Worker 執行上述代碼時,計算過程中頁面正常可操作、無卡頓
//?worker.js
onmessage?=?function?(e)?{
??//?onmessage獲取傳入的初始值
??let?sum?=?e.data;
??for?(let?i?=?0;?i?200000;?i++)?{
????for?(let?i?=?0;?i?10000;?i++)?{
??????sum?+=?Math.random()
????}
??}
??//?將計算的結果傳遞出去
??postMessage(sum);
}
Web Worker 具體的使用與案例,詳情見一文徹底了解 Web Worker,十萬、百萬條數據都是弟弟
Web Worker 的通信時長
并不是執行時間超過 50ms 的任務,就可以使用 Web Worker,還要先考慮通信時長的問題
假如一個運算執行時長為 100ms,但是通信時長為 300ms, 用了 Web Worker 可能會更慢
比如新建一個 web worker, 瀏覽器會加載對應的 worker.js 資源,下圖中的 Time 是這個資源的通信時長(也叫加載時長)
「當任務的運算時長 - 通信時長 > 50ms,推薦使用 Web Worker」
7、e 制作動畫
e是瀏覽器專門為動畫提供的 API,它的刷新頻率與顯示器的頻率保持一致,使用該 api 可以解決用 / 制作動畫卡頓的情況
1)引擎層面
/ 屬于JS引擎,e 屬于GUI引擎
JS引擎與GUI引擎是互斥的,也就是說 GUI 引擎在渲染時會阻塞 JS 引擎的計算
2)時間是否準確
e 刷新頻率是固定且準確的,但 / 是宏任務,根據事件輪詢機制,其他任務會阻塞或延遲 js 任務的執行,會出現定時器不準的情況
3)性能層面
當頁面被隱藏或最小化時,/ 定時器仍會在后臺執行動畫任務,而使用 e 當頁面處于未激活的狀態下,屏幕刷新任務會被系統暫停
8、JS 的 6 種加載方式1)正常模式
<script?src="index.js">script>
這種情況下 JS 會阻塞 dom 渲染,瀏覽器必須等待 index.js 加載和執行完成后才能去做其它事情
2)async 模式
<script?async?src="index.js">script>
async 模式下,它的加載是異步的,JS 不會阻塞 DOM 的渲染,async 加載是無順序的,當它加載結束,JS 會立即執行
使用場景:若該 JS 資源與 DOM 元素沒有依賴關系,也不會產生其他資源所需要的數據時,可以使用 async 模式,比如埋點統計
3)defer 模式
<script?defer?src="index.js">script>
defer 模式下,JS 的加載也是異步的,defer 資源會在 執行之前,并且 defer 是有順序的加載
如果有多個設置了 defer 的 script 標簽存在,則會按照引入的前后順序執行,即便是后面的 script 資源先返回
所以 defer 可以用來控制 JS 文件的執行順序,比如 element-ui.js 和 vue.js,因為 element-ui.js 依賴于 vue,所以必須先引入 vue.js,再引入 element-ui.js
<script?defer?src="vue.js">script>
<script?defer?src="element-ui.js">script>
defer 使用場景:一般情況下都可以使用 defer,特別是需要控制資源加載順序時
4)module 模式
<script?type="module">import?{?a?}?from?'./a.js'script>
在主流的現代瀏覽器中,script 標簽的屬性可以加上 type="module",瀏覽器會對其內部的 import 引用發起 HTTP 請求,獲取模塊內容。這時 script 的行為會像是 defer 一樣,在后臺下載,并且等待 DOM 解析
Vite 就是利用瀏覽器支持原生的es module模塊,開發時跳過打包的過程,提升編譯效率
5) preload
link 標簽的 preload 屬性:用于提前加載一些需要的依賴,這些資源會優先加載(如下圖紅框)
vue2 項目打包生成的 index.html 文件,會自動給首頁所需要的資源,全部添加 preload,實現關鍵資源的提前加載
preload 特點:
1)preload 加載的資源是在瀏覽器渲染機制之前進行處理的,并且不會阻塞 onload 事件;
2)preload 加載的 JS 腳本其加載和執行的過程是分離的,即 preload 會預加載相應的腳本代碼,待到需要時自行調用;
6)
是利用瀏覽器的空閑時間,加載頁面將來可能用到的資源的一種機制;通常可以用于加載其他頁面(非首頁)所需要的資源,以便加快后續頁面的打開速度
.png
特點:
1)pretch 加載的資源可以獲取非當前頁面所需要的資源,并且將其放入緩存至少 5 分鐘(無論資源是否可以緩存)
2)當頁面跳轉時,未完成的 請求不會被中斷
加載方式總結
async、defer 是 script 標簽的專屬屬性,對于網頁中的其他資源,可以通過 link 的 preload、 屬性來預加載
如今現代框架已經將 preload、 添加到打包流程中了,通過靈活的配置,去使用這些預加載功能,同時我們也可以審時度勢地向 script 標簽添加 async、defer 屬性去處理資源,這樣可以顯著提升性能
9、圖片的優化
平常大部分性能優化工作都集中在 JS 方面,但圖片也是頁面上非常重要的部分
特別是對于移動端來說,完全沒有必要去加載原圖,浪費帶寬。如何去壓縮圖片,讓圖片更快的展示出來,有很多優化工作可以做
*請認真填寫需求信息,我們會在24小時內與您取得聯系。