JavaScript通過時(shí)間分片解決頁面卡頓的方法介紹
在前端開發(fā)中,我們時(shí)常會(huì)遇到需要處理大量數(shù)據(jù)渲染的場景 —— 比如一次性插入 10000 條甚至更多數(shù)據(jù)到頁面中。采用 “暴力 破解” 的方式直接同步執(zhí)行,往往會(huì)導(dǎo)致頁面卡頓、白屏,嚴(yán)重影響用戶體驗(yàn)。
時(shí)間分片(Time Slicing) 則是解決這一問題的核心思想,它通過 “拆分任務(wù) + 異步調(diào)度” 的方式,讓 JS 執(zhí)行與頁面渲染互不阻塞,實(shí)現(xiàn)流暢的數(shù)據(jù)處理與展示。
一、問題:為什么大量數(shù)據(jù)直接渲染會(huì)卡頓
要理解時(shí)間分片的價(jià)值,首先我們要從瀏覽器的Event Loop(事件循環(huán)) 機(jī)制入手。
眾所周知,瀏覽器的 JS 線程與渲染線程是 “互斥” 的 —— 也就是說,當(dāng) JS 線程在執(zhí)行同步代碼時(shí),渲染線程會(huì)被阻塞,導(dǎo)致無法更新頁面,只有當(dāng) JS 線程空閑時(shí),渲染線程才會(huì)執(zhí)行頁面重繪。
1.1 暴力 破解的困境:同步執(zhí)行的問題
為了讓大家更好的感受到普通方法和時(shí)間分片的區(qū)別,現(xiàn)在,我將先展示最直接的暴力 破解法,其核心邏輯就是通過for循環(huán)同步創(chuàng)建 10000 個(gè)li元素并插入頁面:
<ul id="container"></ul>
<script>
let now = Date.now();
const total = 100000;
let ul = document.getElementById('container');
// 同步循環(huán)創(chuàng)建并插入元素
for(let i = 0; i < total; i++){
let li = document.createElement('li');
li.innerText = Math.random()*total;
ul.appendChild(li);
}
console.log('JS運(yùn)行時(shí)間', Date.now() - now); // 看似JS執(zhí)行快
setTimeout(()=>{
console.log('總運(yùn)行時(shí)間', Date.now() - now); // 實(shí)際總耗時(shí)遠(yuǎn)更長
},0);
</script>
這是運(yùn)行結(jié)束后的運(yùn)行時(shí)間:

從這里可以看出,暴力 破解法問題如下:
- JS 線程阻塞:100000 次
createElement、appendChild是同步任務(wù),會(huì)占據(jù) JS 線程較長時(shí)間(即使 JS 本身執(zhí)行耗時(shí)短,DOM 操作的開銷也會(huì)累積); - 渲染被推遲:在 JS 同步任務(wù)執(zhí)行期間,渲染線程完全被阻塞,頁面無法更新,用戶會(huì)看到 “白屏” 或 “卡頓”,直到所有 JS 任務(wù)結(jié)束后,渲染線程才會(huì)一次性完成所有元素的繪制;
- 用戶體驗(yàn)差:若數(shù)據(jù)量更大(比如 100 萬條),那么很可能導(dǎo)致瀏覽器出現(xiàn) “無響應(yīng)” 。
二、時(shí)間分片思想
時(shí)間分片的本質(zhì)是 **“化整為零” —— 將原本需要一次性完成的大量任務(wù)(如 100000 條數(shù)據(jù)渲染),拆分成多個(gè)小批次任務(wù)(如每次渲染 10 條),并通過異步調(diào)度機(jī)制 **(如setTimeout、requestAnimationFrame)讓這些小任務(wù)在 JS 線程空閑時(shí)執(zhí)行。
其核心邏輯符合 Event Loop 的調(diào)度規(guī)則:
- 執(zhí)行一小批任務(wù)(如渲染 10 條數(shù)據(jù)),耗時(shí)極短(通常 < 16ms,遠(yuǎn)小于屏幕刷新周期);
- 釋放 JS 線程,讓渲染線程執(zhí)行頁面更新(此時(shí)用戶能看到已渲染的部分?jǐn)?shù)據(jù),無白屏);
- 等待下一次異步調(diào)度時(shí)機(jī),重復(fù)執(zhí)行下一批任務(wù),直到所有數(shù)據(jù)處理完成。
通過這種方式,JS 執(zhí)行與頁面渲染交替進(jìn)行,既完成了大量數(shù)據(jù)處理,又保證了頁面的流暢性。
三、時(shí)間分片的實(shí)現(xiàn)方式
3.1 基礎(chǔ)實(shí)現(xiàn):基于 setTimeout 的異步調(diào)度
setTimeout是最基礎(chǔ)的異步調(diào)度 API,它能將任務(wù)推入 “宏任務(wù)隊(duì)列”,待當(dāng)前同步任務(wù)執(zhí)行完畢、微任務(wù)隊(duì)列清空后,再等待指定時(shí)間(此處設(shè)為 0ms,即盡快執(zhí)行)執(zhí)行。
代碼示例:
<ul id="container"></ul>
<script>
let ul = document.getElementById('container');
const total = 100000; // 總數(shù)據(jù)量
const once = 50; // 每次渲染50條(批次大小,可調(diào)整)
let index = 0; // 當(dāng)前渲染的起始索引
// 遞歸執(zhí)行分片任務(wù)
const loop = (curTotal, curIndex) => {
// 終止條件:剩余數(shù)據(jù)為0時(shí)停止
if (curTotal <= 0) return false;
// 計(jì)算當(dāng)前批次需渲染的數(shù)量(避免最后一批不足10條)
const pageCount = Math.min(curTotal, once);
// 異步執(zhí)行當(dāng)前批次渲染
setTimeout(() => {
for (let i = 0; i < pageCount; i++) {
const li = document.createElement('li');
li.innerText = `${curIndex + i}: ${Math.random() * total}`;
ul.appendChild(li);
}
// 遞歸執(zhí)行下一批:剩余數(shù)據(jù)量=當(dāng)前總量-本批數(shù)量,起始索引=當(dāng)前索引+本批數(shù)量
loop(curTotal - pageCount, curIndex + pageCount);
}, 0);
};
// 啟動(dòng)分片渲染
loop(total, index);
</script>
核心邏輯解析:
- 批次大?。╫nce) :設(shè)為 50 是權(quán)衡 “渲染效率” 與 “流暢度” 的結(jié)果 —— 批次太小會(huì)增加異步調(diào)度次數(shù),批次太大仍可能阻塞,這個(gè)數(shù)據(jù)可根據(jù)實(shí)際數(shù)據(jù)量調(diào)整。
- 遞歸終止:當(dāng)
curTotal(剩余數(shù)據(jù)量)≤0 時(shí),停止遞歸,避免無限調(diào)用; - 異步調(diào)度:
setTimeout(fn, 0)確保當(dāng)前批次的 DOM 操作在 “下一次宏任務(wù)” 中執(zhí)行,此時(shí) JS 線程會(huì)先釋放,讓渲染線程更新已完成的部分。
不足:可能存在 “輕微白屏”
setTimeout的調(diào)度時(shí)機(jī)由 JS 引擎決定,不一定與瀏覽器的 “屏幕刷新周期”(通常為 60Hz,即每 16.6ms 刷新一次)同步。
若宏任務(wù)執(zhí)行時(shí)機(jī)與渲染時(shí)機(jī)錯(cuò)位,在我們拖動(dòng)進(jìn)度條時(shí),可能導(dǎo)致短暫的 “白屏”(雖比同步執(zhí)行好很多,但體驗(yàn)仍有優(yōu)化空間)。

3.2 優(yōu)化實(shí)現(xiàn):基于 requestAnimationFrame 的渲染同步
為解決setTimeout的 “時(shí)機(jī)錯(cuò)位” 問題,可使用瀏覽器原生 API——requestAnimationFrame(rAF) 。它的核心優(yōu)勢是:確?;卣{(diào)函數(shù)在 “屏幕每一次刷新前” 執(zhí)行,與渲染周期完全同步,不會(huì)丟幀,徹底解決白屏問題。
實(shí)現(xiàn)代碼(文檔示例):
<ul id="container"></ul>
<script>
let ul = document.getElementById('container');
const total = 100000;
const once = 50;
let index = 0;
const loop = (curTotal, curIndex) => {
if (curTotal <= 0) return false;
const pageCount = Math.min(curTotal, once);
// 用rAF替代setTimeout,與屏幕刷新同步
requestAnimationFrame(() => {
for (let i = 0; i < pageCount; i++) {
const li = document.createElement('li');
li.innerText = `${curIndex + i}: ${(Math.random() * total).toFixed(2)}`;
ul.appendChild(li);
}
loop(curTotal - pageCount, curIndex + pageCount);
});
};
loop(total, index);
</script>
核心優(yōu)勢:
- 渲染同步:rAF 的回調(diào)會(huì)在瀏覽器準(zhǔn)備好重繪時(shí)觸發(fā)(約每 16.6ms 一次),確保當(dāng)前批次的 DOM 操作完成后,渲染線程能立即更新頁面,用戶看到的是 “逐步流暢加載”,無任何白屏;
- 性能友好:若瀏覽器標(biāo)簽頁處于 “后臺(tái)”,rAF 會(huì)自動(dòng)暫停,避免浪費(fèi) CPU 資源(
setTimeout仍會(huì)執(zhí)行)。
適用場景:
對(duì)渲染流暢度要求高的場景,如長列表渲染、大數(shù)據(jù)表格展示等。
四、兩種實(shí)現(xiàn)方式的對(duì)比
| 特性 | setTimeout 實(shí)現(xiàn) | requestAnimationFrame 實(shí)現(xiàn) |
|---|---|---|
| 調(diào)度時(shí)機(jī) | 宏任務(wù)隊(duì)列,時(shí)機(jī)不固定 | 與屏幕刷新同步(≈16.6ms / 次) |
| 白屏問題 | 可能存在輕微白屏 | 無白屏,渲染流暢 |
| 后臺(tái)運(yùn)行 | 仍會(huì)執(zhí)行,消耗 CPU | 后臺(tái)暫停,節(jié)省資源 |
| 兼容性 | 所有瀏覽器支持 | IE9 + 支持(現(xiàn)代瀏覽器均兼容) |
| 適用場景 | 對(duì)流暢度要求不高的簡單場景 | 長列表、大數(shù)據(jù)渲染等核心場景 |
到此這篇關(guān)于JavaScript通過時(shí)間分片解決頁面卡頓的方法介紹的文章就介紹到這了,更多相關(guān)JavaScript時(shí)間分片內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript使用Promise實(shí)現(xiàn)分批處理接口請(qǐng)求
當(dāng)我們在實(shí)際項(xiàng)目中遇到需要批量發(fā)起上百條接口請(qǐng)求怎么辦呢,本文就來為大家介紹一下JavaScript如何使用Promise實(shí)現(xiàn)分批處理接口請(qǐng)求,需要的小伙伴可以參考一下2023-11-11
uniapp實(shí)現(xiàn)上拉加載更多功能的全過程
我們在項(xiàng)目中經(jīng)常使用到上拉加載更多,下面這篇文章主要給大家介紹了關(guān)于uniapp實(shí)現(xiàn)上拉加載更多功能的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-10-10
JS實(shí)現(xiàn)圓形進(jìn)度條拖拽滑動(dòng)
這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)圓形進(jìn)度條拖拽滑動(dòng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10
js判斷滾動(dòng)條是否已到頁面最底部或頂部實(shí)例
這篇文章主要介紹了js判斷滾動(dòng)條是否已到頁面最底部或頂部的原理與方法,以實(shí)例的形式詳細(xì)分析了js實(shí)現(xiàn)返回頂部功能所涉及的各種技巧,并對(duì)相關(guān)知識(shí)點(diǎn)進(jìn)行了總結(jié)歸納,需要的朋友可以參考下2014-11-11
基于Bootstrap 3 JQuery及RegExp的表單驗(yàn)證功能
這篇文章主要介紹了基于Bootstrap 3 JQuery及RegExp的表單驗(yàn)證功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-02-02
JS實(shí)現(xiàn)針對(duì)給定時(shí)間的倒計(jì)時(shí)功能示例
這篇文章主要介紹了JS實(shí)現(xiàn)針對(duì)給定時(shí)間的倒計(jì)時(shí)功能,結(jié)合具體實(shí)例形式分析了javascript日期時(shí)間的正則判定與動(dòng)態(tài)運(yùn)算相關(guān)操作技巧,需要的朋友可以參考下2017-04-04
JavaScript組合設(shè)計(jì)模式--改進(jìn)引入案例分析
這篇文章主要介紹了JavaScript組合設(shè)模式改進(jìn)引入案例,結(jié)合實(shí)例形式分析了JavaScript組合設(shè)計(jì)模式特性改進(jìn)的引入示例相關(guān)操作技巧,需要的朋友可以參考下2020-05-05

