引言:为什么您的网站“卡顿”,以及谁该为此负责
\n\n想象一家餐厅,一位服务员同时负责点单、上菜、洗碗和结账。一片混乱,对吧?单线程的 JavaScript 大致也是如此,如果任务管理不当的话。当我们编写的代码试图同时处理所有事情——加载数据、处理点击、绘制动画——浏览器就会卡住,用户看到的是一个“反应迟钝”的界面。
\n\n今天,我们将探讨 JavaScript 任务队列 如何帮助将这种混乱转变为有序的系统。您将了解 Event Loop 如何分配优先级,微任务与宏任务有何不同,以及 JavaScript 后台任务 如何避免阻塞主线程。最后,您将看到一些可以直接应用到您项目中的实用示例。
\n\nEvent Loop 的工作原理:任务队列的指挥家
\n\n所有异步操作的核心是 Event Loop JavaScript。这是一个不断循环的机制,它会询问:“有任务要执行吗?有微任务吗?需要渲染吗?”如果您曾经好奇为什么 setTimeout 不会立即执行,而 Promise.then 几乎瞬间完成——答案就在这里。
调用栈 (Call Stack)
\n这是函数被立即执行的地方。只要调用栈被占用,其他任何事情都不会发生。如果您的函数要计算一百万个数字——浏览器就会卡死,因为调用栈不会被释放。
\n\n宏任务队列 (Macrotask Queue)
\nsetTimeout、setInterval、DOM 事件(点击、滚动)、资源加载等会进入此队列。Event Loop 每次从这个队列中取出一个任务执行,然后检查微任务,接着渲染页面——然后再取下一个宏任务。
微任务队列 (Microtask Queue)
\n这里是 Promise、queueMicrotask、MutationObserver 的领地。微任务拥有最高优先级。一旦调用栈清空,Event Loop 会执行 所有 微任务,然后才处理下一个宏任务。这就是为什么 Promise.resolve().then() 比 setTimeout 执行得更快。
console.log('1');\n\nsetTimeout(() => {\n console.log('2');\n}, 0);\n\nPromise.resolve().then(() => {\n console.log('3');\n});\n\nconsole.log('4');\n\n// 输出: 1, 4, 3, 2\n\n\n这个例子很经典。即使延迟为零,setTimeout(宏任务)也会被推迟,而 Promise.then(微任务)会在同步代码之后立即执行。理解这个顺序是编写可预测异步代码的关键。
后台任务:如何避免阻塞界面
\n\n现在,主要问题来了:如果需要执行繁重的工作(图像处理、复杂计算、数据解析)该怎么办?如果在主线程中运行这些任务——界面就会卡死。解决方案是 JavaScript 后台任务。
\n\nWeb Workers — 真正的多线程
\nWeb Workers JS 允许在单独的线程中运行代码。它无法访问 DOM,但可以与主线程交换消息。这是 JS 队列处理 在后台运行的理想工具。
\n\n// main.js\nconst worker = new Worker('worker.js');\n\nworker.postMessage({ data: [1, 2, 3, 4, 5, 6] });\n\nworker.onmessage = (event) => {\n console.log('来自 Worker 的结果:', event.data);\n};\n\n// worker.js\nself.onmessage = (event) => {\n const numbers = event.data.data;\n // 繁重工作:过滤偶数\n const result = numbers.filter(n => n % 2 === 0);\n self.postMessage(result);\n};\n\n\n请注意:主线程没有被阻塞。用户可以点击、滚动、输入文本——所有操作都会流畅进行,而 Worker 在后台计算。
\n\n