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