表格数据处理导致页面卡顿,Long Task怎么优化?
最近在做表格数据处理功能,当用户导入超过1w条数据时页面会卡死几秒,Lighthouse检测到Long Task有12秒。我尝试把循环改成用setTimeout分片处理,但实际运行时还是出现长时间阻塞,控制台提示”Task took 5133ms”。这是为什么呢?
代码大概是这样:
let data = [];
for (let i = 0; i < 10000; i++) {
// 复杂的格式转换和计算
data.push(transformRow(rawData[i]));
}
renderTable(data);
改成分片后:
function processChunk(start, end) {
for (let i = start; i < end; i++) {
data.push(transformRow(rawData[i]));
}
if (end < 10000) {
setTimeout(() => processChunk(end, end + 100), 0);
} else {
renderTable(data);
}
}
processChunk(0, 100);
但实际测试时,处理过程依然导致页面无法点击按钮,这是哪里出问题了?
setTimeout分片,但每次分片处理的数据量还是太大,导致单次任务依然耗时过长。浏览器的事件循环机制决定了,即使你用setTimeout,只要你的回调函数执行时间太长,还是会阻塞主线程。前端这块,表格数据处理这种场景其实很常见,优化的关键是确保每次分片的任务足够轻量,同时让出时间给浏览器处理其他任务,比如用户交互或渲染。
你可以试试用
requestIdleCallback或者更可控的Promise + setTimeout来实现真正的微任务分片。下面是一个改进版本:这里有几个关键点:
1. 每次分片的任务用
performance.now()控制执行时间,确保不会超过一个阈值(比如50ms)。这个时间可以根据实际需求调整。2. 通过
Promise和setTimeout结合的方式,确保每个分片完成后主动让出主线程。3. 整体流程用
async/await包装起来,代码逻辑清晰且容易维护。另外,如果你的数据处理逻辑特别复杂,比如涉及到大量的计算或者格式转换,可以考虑用 Web Worker 把这些计算放到后台线程里去跑。这样完全不会阻塞主线程,用户体验会好很多。
最后提醒一下,别忘了对
transformRow函数本身做性能分析,看看有没有优化空间。很多时候问题并不在循环本身,而是在每次循环里的具体操作。