JS時(shí)間分片技術(shù)解決長(zhǎng)任務(wù)導(dǎo)致的頁面卡頓
起因
同事遇到一個(gè)動(dòng)畫展示的問題,就是下面要執(zhí)行一個(gè)運(yùn)算量很大的函數(shù),他要加載一個(gè) loading,但他發(fā)現(xiàn)把 loading 的元素 display: block; 頁面中也不會(huì)立刻出現(xiàn) loading 動(dòng)畫,出現(xiàn)動(dòng)畫的時(shí)候是運(yùn)算函數(shù)執(zhí)行完畢之后。
處理辦法
有兩種方法去處理這種耗時(shí)任務(wù),第一種就是 webWorker,但是一些 dom 的操作做不了,于是就想到了通過 generator 函數(shù)來解決,下面先簡(jiǎn)單了解下事件循環(huán)。
事件循環(huán)
微任務(wù):
1. Promise.then
2. Object.observe
3. MutaionObserver
宏任務(wù):
1. script(整體代碼)
2. setTimeout
3. setInterval
4. I/O
5. postMessage
6. MessageChannel
瀏覽器渲染時(shí)機(jī)
除去特殊情況,頁面的渲染會(huì)在微任務(wù)隊(duì)列清空后,宏任務(wù)執(zhí)行前,所以我們可以讓推入主執(zhí)行棧的函數(shù)執(zhí)行到一定時(shí)間就去休眠,然后在渲染之后的宏任務(wù)里面叫醒他,這樣渲染或者用戶交互都不會(huì)卡頓了!
原始代碼
我們先模擬一個(gè) js 長(zhǎng)任務(wù)
代碼
// style @keyframes move { from { left: 0; } to { left: 100%; } } .move { position: absolute; animation: move 5s linear infinite; } // dom <div class="move">123123123</div> // script function fnc () { let i = 0 const start = performance.now() while (performance.now() - start <= 5000) { i++ } return i } setTimeout(() => { fnc() }, 1000)
效果
如下圖,動(dòng)畫運(yùn)行 1s 的時(shí)候,js 函數(shù)開始運(yùn)行,動(dòng)畫會(huì)先停止渲染,然后等 js 主執(zhí)行??臻e之后動(dòng)畫才繼續(xù)進(jìn)行。
函數(shù)改造
我們把原來的函數(shù)改造為 generator 函數(shù)
代碼
// generator 處理原來的函數(shù) function * fnc_ () { let i = 0 const start = performance.now() while (performance.now() - start <= 5000) { yield i++ } return i } // 簡(jiǎn)易時(shí)間分片 function timeSlice (fnc, cb = setTimeout) { if(fnc.constructor.name !== 'GeneratorFunction') return fnc() return async function (...args) { const fnc_ = fnc(...args) let data do { data = fnc_.next(await data?.value) // 每執(zhí)行一步就休眠,注冊(cè)一個(gè)宏任務(wù) setTimeout 來叫醒他 await new Promise( resolve => cb(resolve)) } while (!data.done) return data.value } } setTimeout(async () => { const fnc = timeSlice(fnc_) const start = performance.now() console.log('開始') const num = await fnc() console.log('結(jié)束', `${(performance.now() - start)/ 1000}s`) console.log(num) }, 1000)
效果
動(dòng)畫根本不受影響,fps 一直很穩(wěn)定,因?yàn)槲覀儼押臅r(shí)任務(wù)拆成很多個(gè)塊來執(zhí)行。
優(yōu)化時(shí)間分片
上面的時(shí)間分片函數(shù)每執(zhí)行一步,就會(huì)休眠,然后通過一個(gè)宏任務(wù)來喚醒他,但是這樣的執(zhí)行效率肯定是比較低的,我們?cè)賰?yōu)化一下執(zhí)行的效率,提升連續(xù)執(zhí)行時(shí)間。
代碼
// 精準(zhǔn)時(shí)間分片 function timeSlice_ (fnc, time = 25, cb = setTimeout) { if(fnc.constructor.name !== 'GeneratorFunction') return fnc() return function (...args) { const fnc_ = fnc(...args) let data return new Promise(async function go (resolve, reject) { try { const start = performance.now() do { data = fnc_.next(await data?.value) } while (!data.done && performance.now() - start < time) if (data.done) return resolve(data.value) cb(() => go(resolve, reject)) } catch(e) { reject(e) } }) } } setTimeout(async () => { const fnc1 = timeSlice_(fnc_) let start = performance.now() console.log('開始') const num = await fnc1() console.log('結(jié)束', `${(performance.now() - start)/ 1000}s`) console.log(num) }, 1000);
效果
我們把函數(shù)分成了較大的塊,這樣函數(shù)執(zhí)行的效率就會(huì)變高,fps 會(huì)稍微收到影響,但是在接受范圍內(nèi)。
對(duì)比優(yōu)化前后
我們對(duì)比一下優(yōu)化時(shí)間分片函數(shù)前后的效果
代碼
setTimeout(async () => { const fnc = timeSlice(fnc_) const fnc1 = timeSlice_(fnc_) let start = performance.now() console.log('開始') const a = await fnc() console.log('結(jié)束', `${(performance.now() - start)/ 1000}s`) console.log('開始') start = performance.now() const b = await fnc1() console.log('結(jié)束', `${(performance.now() - start)/ 1000}s`) console.log(a, b) }, 1000);
效果
對(duì)比優(yōu)化后的時(shí)間分片函數(shù),是之前效率的 4452 倍,我們做的只是提升了函數(shù)連續(xù)執(zhí)行時(shí)間。
最后
generator 函數(shù)中 yield 的位置非常關(guān)鍵,需要放到耗時(shí)的地方,優(yōu)化后的時(shí)間分片函數(shù)也提供了 time 變量,你可以根據(jù)實(shí)際情況來改變你的 time 值。
以上就是JS時(shí)間分片技術(shù)解決長(zhǎng)任務(wù)導(dǎo)致的頁面卡頓的詳細(xì)內(nèi)容,更多關(guān)于js時(shí)間分片長(zhǎng)任務(wù)分解的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
微信小程序?qū)崿F(xiàn)錨點(diǎn)定位樓層跳躍的實(shí)例
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)錨點(diǎn)定位樓層跳躍的實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-05-05JavaScript深拷貝方法structuredClone使用
這篇文章主要為大家介紹了JavaScript深拷貝方法structuredClone使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02JS中的every()對(duì)空數(shù)組總返回true原理分析
這篇文章主要為大家介紹了JS中的every()對(duì)空數(shù)組總返回true原理分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09微信小程序 使用騰訊地圖SDK詳解及實(shí)現(xiàn)步驟
這篇文章主要介紹了微信小程序 使用騰訊地圖SDK詳解及實(shí)現(xiàn)步驟的相關(guān)資料,需要的朋友可以參考下2017-02-02微信小程序 藍(lán)牙的實(shí)現(xiàn)實(shí)例代碼
這篇文章主要介紹了微信小程序 藍(lán)牙的實(shí)現(xiàn)實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-06-06uniApp學(xué)習(xí)之熱門搜索,搜索數(shù)據(jù)頁面緩存實(shí)例
這篇文章主要介紹了uniApp學(xué)習(xí)之熱門搜索,搜索數(shù)據(jù)頁面緩存實(shí)例,需要的朋友可以參考下2023-10-10