JavaScript前端優(yōu)化策略深入詳解
虛擬滾動
當我們開發(fā)的時候,遇到大數(shù)據(jù)加載,頁面卡頓的問題應該如何處理?大多數(shù)情況下,我們都是盡量通過分頁的方式處理這類問題,但是總有一些特殊的情況我們必須把數(shù)據(jù)全部加載到前端進行處理。我曾經(jīng)遇到過一個坑爹的組件,必須一次性加載幾萬條數(shù)據(jù)到頁面上,導致一打開這個組件,就會讓頁面瘋狂卡頓,當時便用了虛擬滾動解決了這個棘手的問題。
所以當我們遇到這種情況:渲染長列表的場景,當渲染條數(shù)過多時,所需要的渲染時間會很長,滾動時還會造成頁面卡頓,整體體驗非常不好。完全可以考慮使用虛擬滾動這個解決方案。
虛擬滾動——指的是只渲染可視區(qū)域的列表項,非可見區(qū)域的不渲染,在滾動時動態(tài)更新可視區(qū)域,該方案在優(yōu)化大量數(shù)據(jù)渲染時效果是很明顯的。以下這個圖較為清晰地解釋了虛擬滾動的工作原理。
虛擬滾動基本原理:
計算出 totalHeight 列表總高度,并在觸發(fā)時滾動事件時根據(jù) scrollTop 值不斷更新 startIndex 以及 endIndex ,以此從列表數(shù)據(jù) listData 中截取對應元素。
虛擬滾動插件
虛擬滾動的使用場景不少,網(wǎng)上有很多已經(jīng)造好的輪子,我們也可以適當?shù)剡x擇更快更便捷的方式解決我們的頁面問題。
虛擬滾動的插件有很多,比如 vue-virtual-scroller、vue-virtual-scroll-list、react-tiny-virtual-list、react-virtualized 等
這里簡單介紹 vue-virtual-scroller 的使用
// 安裝插件 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) // 使用 <template> <RecycleScroller class="scroller" :items="list" :item-size="32" key-field="id" v-slot="{ item }"> <div class="user"> {{ item.name }} </div> </RecycleScroller> </template>
該插件主要有 RecycleScroller.vue、DynamicScroller.vue 這兩個組件,其中 RecycleScroller 需要 item 的高度為靜態(tài)的,也就是列表每個 item 的高度都是一致的,而 DynamicScroller 可以兼容 item 的高度為動態(tài)的情況
Web Worker優(yōu)化長任務
瀏覽器對頁面的渲染時單線程的,導致我們的頁面無法同時渲染CSS和JS代碼。所以由于瀏覽器 GUI 渲染線程與 JS 引擎線程是互斥的關系,當頁面中有很多長任務時,會造成頁面 UI 阻塞,出現(xiàn)界面卡頓、掉幀等情況。
如果直接把下面這段代碼直接丟到主線程中,計算過程中頁面一直處于卡死狀態(tài),無法操作。
let sum = 0; for (let i = 0; i < 200000; i++) { for (let i = 0; i < 10000; i++) { sum += Math.random() } }
使用 Web Worker 執(zhí)行上述代碼時,計算過程中頁面正常可操作、無卡頓
// 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我們也不能濫用,畢竟加載worker也是需要消耗一定的服務器資源。并不是執(zhí)行時間超過 50ms 的任務,就可以使用 Web Worker,還要先考慮通信時長的問題假如一個運算執(zhí)行時長為 100ms,但是通信時長為 300ms, 用了 Web Worker可能會更慢
比如新建一個 web worker, 瀏覽器會加載對應的 worker.js 資源,下圖中的 Time 是這個資源的通信時長(也叫加載時長)。
當任務的運算時長 - 通信時長 > 50ms,推薦使用Web Worker
requestAnimationFrame 制作動畫
頁面如果想要更加吸人眼球,動畫必不可少。但是動畫有時候會讓頁面看起來卡頓,而且也比較消耗瀏覽器的資源。那么為了解決這樣的一些問題,我們可以使用requestAnimationFrame這個API來處理我們的動畫效果。
requestAnimationFrame 是瀏覽器專門為動畫提供的 API,它的刷新頻率與顯示器的頻率保持一致,使用該 api 可以解決用 setTimeout/setInterval 制作動畫卡頓的情況。
①引擎層面
setTimeout/setInterval 屬于 JS引擎,requestAnimationFrame 屬于 GUI引擎JS引擎與GUI引擎是互斥的,也就是說 GUI 引擎在渲染時會阻塞 JS 引擎的計算。
②時間是否準確
requestAnimationFrame 刷新頻率是固定且準確的,但 setTimeout/setInterval 是宏任務,根據(jù)事件輪詢機制,其他任務會阻塞或延遲js任務的執(zhí)行,會出現(xiàn)定時器不準的情況。
③性能層面
當頁面被隱藏或最小化時,setTimeout/setInterval 定時器仍會在后臺執(zhí)行動畫任務,而使用 requestAnimationFrame 當頁面處于未激活的狀態(tài)下,屏幕刷新任務會被系統(tǒng)暫停。
JS的加載方式
關于我們頁面的加載,相信很多人都知道在單線程的加載方式下,很多時候錯誤的編碼會導致頁面的阻塞,從而影響用戶的體驗,那么關于這點,我們應該更詳細地去了解JS的加載方式。
①正常模式
<script src="index.js"></script>
這種情況下 JS 會阻塞 dom 渲染,瀏覽器必須等待 index.js 加載和執(zhí)行完成后才能去做其它事情。也就是說如果整個頁面跑下來,遇到這行代碼將會直接停止頁面dom的渲染,等到js文件加載完畢后,才能夠再去繪制頁面。
②async 模式
<script async src="index.js"></script>
async 模式下,它的加載是異步的,JS 不會阻塞 DOM 的渲染,async 加載是無順序的,當它加載結束,JS 會立即執(zhí)行。
使用場景:若該 JS 資源與 DOM 元素沒有依賴關系,也不會產(chǎn)生其他資源所需要的數(shù)據(jù)時,可以使用async 模式,比如埋點統(tǒng)計。
③defer 模式
<script defer src="index.js"></script>
defer 模式下,JS 的加載也是異步的,defer 資源會在 DOMContentLoaded 執(zhí)行之前,并且 defer 是有順序的加載。
如果有多個設置了 defer 的 script 標簽存在,則會按照引入的前后順序執(zhí)行,即便是后面的 script 資源先返回。所以 defer 可以用來控制 JS 文件的執(zhí)行順序,比如 element-ui.js 和 vue.js,因為 element-ui.js 依賴于 vue,所以必須先引入 vue.js,再引入 element-ui.js。
defer 使用場景:一般情況下都可以使用 defer,特別是需要控制資源加載順序時
④module 模式
<script type="module">import {<!--{C}%3C!%2D%2D%20%2D%2D%3E--> a } from './a.js'</script>
在主流的現(xiàn)代瀏覽器中,script 標簽的屬性可以加上 type=“module”,瀏覽器會對其內(nèi)部的 import 引用發(fā)起 HTTP 請求,獲取模塊內(nèi)容。這時 script 的行為會像是 defer 一樣,在后臺下載,并且等待 DOM 解析。
Vite 就是利用瀏覽器支持原生的 es module 模塊,開發(fā)時跳過打包的過程,提升編譯效率。
⑤preload 模式
<link rel="preload" as="script" href="index.js" rel="external nofollow" rel="external nofollow" >
link 標簽的 preload 屬性:用于提前加載一些需要的依賴,這些資源會優(yōu)先加載。
1)preload 加載的資源是在瀏覽器渲染機制之前進行處理的,并且不會阻塞 onload 事件;
2)preload 加載的 JS 腳本其加載和執(zhí)行的過程是分離的,即 preload 會預加載相應的腳本代碼,待到需要時自行調(diào)用;
⑥prefetch 模式
<link rel="prefetch" as="script" href="index.js" rel="external nofollow" rel="external nofollow" >
prefetch 是利用瀏覽器的空閑時間,加載頁面將來可能用到的資源的一種機制;通??梢杂糜诩虞d其他頁面(非首頁)所需要的資源,以便加快后續(xù)頁面的打開速度。
prefetch 特點:
1)pretch 加載的資源可以獲取非當前頁面所需要的資源,并且將其放入緩存至少5分鐘(無論資源是否可以緩存)
2)當頁面跳轉時,未完成的 prefetch 請求不會被中斷
加載方式的總結
async、defer 是 script 標簽的專屬屬性,對于網(wǎng)頁中的其他資源,可以通過 link 的 preload、prefetch 屬性來預加載。
如今現(xiàn)代框架已經(jīng)將 preload、prefetch 添加到打包流程中了,通過靈活的配置,去使用這些預加載功能,同時我們也可以審時度勢地向 script 標簽添加 async、defer 屬性去處理資源,這樣可以顯著提升性能。 所以通常情況下,當我們使用了打包工具時,也不需要我們?nèi)ス芾硖鄰碗s的加載形式,只需要通過簡單的配置即可達到我們想要的效果。
到此這篇關于JavaScript前端優(yōu)化策略深入詳解的文章就介紹到這了,更多相關JS前端優(yōu)化策略內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
基于JavaScript實現(xiàn)年月日三級聯(lián)動
這篇文章主要為大家詳細介紹了基于JavaScript實現(xiàn)年月日三級聯(lián)動,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-06-06