Skip to content

如果需要使用 JS 执行 100 万个任务,如何保证浏览器不卡顿?

1. 使用分块处理(Chunking)

将 100 万个任务分成小块,逐块处理,每块处理完成后将控制权交还给浏览器,利用空闲时间继续处理。

实现方式:setTimeoutsetInterval

javascript
function processInChunks(tasks, chunkSize = 100) {
  function processChunk() {
    const chunk = tasks.splice(0, chunkSize);
    chunk.forEach(task => task());
    if (tasks.length > 0) {
      setTimeout(processChunk, 0); // 让出主线程
    }
  }
  processChunk();
}

// 示例
const tasks = Array.from({ length: 1000000 }, (_, i) => () => console.log(i));
processInChunks(tasks);

2. 使用浏览器空闲时间:requestIdleCallback

requestIdleCallback 可以利用浏览器的空闲时间执行任务,避免占用主线程的关键资源时间。

javascript
function processWithIdleCallback(tasks) {
  function processChunk(deadline) {
    while (deadline.timeRemaining() > 0 && tasks.length > 0) {
      const task = tasks.shift();
      task();
    }
    if (tasks.length > 0) {
      requestIdleCallback(processChunk);
    }
  }
  requestIdleCallback(processChunk);
}

// 示例
const tasks = Array.from({ length: 1000000 }, (_, i) => () => console.log(i));
processWithIdleCallback(tasks);

3. 使用 Web Workers

将任务放到 Web Worker 中执行,避免阻塞主线程。

主线程代码:

javascript
const worker = new Worker("worker.js");
worker.postMessage(1000000); // 发送任务数量
worker.onmessage = (e) => {
  console.log(e.data); // 接收 worker 处理结果
};

Worker 脚本(worker.js):

javascript
onmessage = (e) => {
  const tasks = e.data;
  for (let i = 0; i < tasks; i++) {
    // 模拟任务
  }
  postMessage("All tasks completed!");
};

4. 批处理和任务调度器

创建自定义任务调度器,根据优先级和剩余时间动态分配任务。

javascript
class Scheduler {
  constructor() {
    this.tasks = [];
  }

  add(task) {
    this.tasks.push(task);
  }

  run(chunkSize = 100) {
    const execute = () => {
      const chunk = this.tasks.splice(0, chunkSize);
      chunk.forEach(task => task());
      if (this.tasks.length > 0) {
        setTimeout(execute, 0);
      }
    };
    execute();
  }
}

// 示例
const scheduler = new Scheduler();
for (let i = 0; i < 1000000; i++) {
  scheduler.add(() => console.log(i));
}
scheduler.run();

5. 使用 async/await 与微任务

利用 Promiseawait 将任务切分到微任务队列中,减少对主线程的持续占用。

javascript
async function processTasks(tasks) {
  for (let i = 0; i < tasks.length; i++) {
    tasks[i]();
    if (i % 100 === 0) {
      await new Promise(resolve => setTimeout(resolve, 0)); // 让出主线程
    }
  }
}

// 示例
const tasks = Array.from({ length: 1000000 }, (_, i) => () => console.log(i));
processTasks(tasks);

题目要点:

为了保证浏览器不卡顿,核心思路是避免长时间占用主线程,让浏览器有机会执行渲染和其他任务。

具体的方法有:

  1. 使用 分块处理空闲时间调度
  2. 利用 Web Workers 将任务移到子线程。
  3. 结合 Promise异步操作 处理任务。