重任务性能优化
2025/2/20大约 2 分钟
重任务性能优化
前言
在前端开发中,我们经常遇到一些计算量大的任务:数据处理、图表预计算、循环遍历、密集数学运算等。如果不小心放在主线程中同步执行,很容易导致:
- 页面卡顿、掉帧;
- 动画不流畅;
- 用户点击无响应;
- “页面无响应”警告。
1. 问题再现:百万次循环,页面瞬间卡死
let total = 0;
for (let i = 0; i < 1_000_000; i++) {
total += i;
}
count.value = total;
为什么?
- 这段代码是同步执行的;
- JavaScript 单线程运行,会阻塞主线程;
- 在阻塞期间,浏览器无法渲染页面或响应用户操作;
- 用户体验极差,轻则掉帧,重则直接页面“假死”。
2. 浏览器的工作原理(简化)
- 浏览器每秒尝试绘制 60 帧(即每帧 ≈ 16.67ms);
- 如果 JavaScript 在某一帧中执行耗时超过 16.67ms,下一帧就来不及渲染;
- 连续的掉帧就会产生“卡顿”体验。
3. 优化方案
1. 分片执行封装(基于 requestAnimationFrame)
export function runHeavyTaskInChunks(taskFn, total, chunkSize = 1000, onDone) {
let index = 0;
function step() {
const end = Math.min(index + chunkSize, total);
for (; index < end; index++) {
taskFn(index);
}
if (index < total) {
requestAnimationFrame(step);
} else if (onDone) {
onDone();
}
}
requestAnimationFrame(step);
}
使用示例:
let total = 0;
runHeavyTaskInChunks(
(i) => {
total += i;
},
1_000_000,
10000,
() => {
count.value = total;
},
);
2. requestIdleCallback:后台空闲时计算
let i = 0;
let total = 0;
function processChunk(deadline) {
while (i < 1_000_000 && deadline.timeRemaining() > 0) {
total += i++;
}
if (i < 1_000_000) {
requestIdleCallback(processChunk);
} else {
count.value = total;
}
}
requestIdleCallback(processChunk);
3. Web Worker:真正的多线程执行
worker.js
self.onmessage = () => {
let total = 0;
for (let i = 0; i < 1_000_000; i++) {
total += i;
}
self.postMessage(total);
};
主线程使用
const worker = new Worker(new URL("./worker.js", import.meta.url));
worker.onmessage = (e) => {
count.value = e.data;
};
worker.postMessage("start");
4. 缓存计算结果
const cache = new Map();
function expensiveCalc(n) {
if (cache.has(n)) return cache.get(n);
let total = 0;
for (let i = 0; i < n; i++) total += i;
cache.set(n, total);
return total;
}
4. 各种优化方案对比
方案 | 是否阻塞主线程 | 执行控制 | 适用场景 |
---|---|---|---|
同步执行 | 是 | 快速执行 | 小任务或立即计算 |
分片(RAF)执行 | 否 | 较灵活 | 与 UI 同步,逐帧计算 |
requestIdleCallback | 否 | 不可控 | 不紧急、后台计算 |
Web Worker | 否 | 完全可控 | 高计算密度,大数据处理 |
缓存优化 | 否 | 快速返回 | 可复用结果、函数纯净 |
总结:根据场景选择
- 如果是UI相关、用户交互触发的任务:用
requestAnimationFrame
分帧处理; - 如果是懒加载、后台逻辑:用
requestIdleCallback
; - 如果是重型 CPU 计算:果断上 Web Worker;
- 如果任务可复用:加缓存最简单。