JavaScript 的事件循环机制(EventLoop)是 JavaScript 运行时环境(如浏览器和 Node.js)中用于处理异步事件和回调的一种机制。它允许 JavaScript 代码在单线程中运行,同时又能执行异步操作(如网络请求、文件读取等),而不会阻塞主线程的执行。
JavaScript 运行环境
在 JavaScript 中,有一个调用栈(Call Stack)用于存储当前执行中的函数调用。当代码执行时,它会被压入调用栈,当函数执行完成后,它会被从调用栈中弹出。由于 JavaScript 是单线程的,同一时间只能有一个任务在调用栈中执行。
异步操作和事件队列
当 JavaScript 执行异步操作时(如 setTimeout、Promise、网络请求等),这些操作不会立即执行,而是会被放到任务队列(Task Queue)或微任务队列(Microtask Queue)中等待。这些队列与调用栈是分离的,但它们会在某个时刻与调用栈进行交互。
- 任务队列(Task Queue):通常用于处理宏任务(Macrotasks),如 setTimeout、setInterval、I/O、UI 渲染等。
- 微任务队列(Microtask Queue):用于处理微任务(Microtasks),如 Promise.then、MutationObserver 等。微任务通常比宏任务有更高的优先级,会在每个宏任务执行结束后立即执行。
事件循环
事件循环是 JavaScript 运行时环境中用于不断循环执行以下步骤的机制:
检查调用栈:如果调用栈为空,则继续执行;如果调用栈不为空,则等待调用栈中的任务执行完毕。
执行微任务队列:一旦调用栈为空,事件循环会查看微任务队列。如果微任务队列中有任务,它会将队列中的所有微任务依次取出并执行,直到微任务队列为空。每执行完一个宏任务后,都会立即执行完所有的微任务。
执行宏任务:在微任务队列为空后,事件循环会查看任务队列。如果任务队列中有任务,它会取出队列中的第一个宏任务放入调用栈执行。执行完毕后,再次回到第一步,检查调用栈是否为空,并执行微任务队列中的任务(如果有的话),然后再次从任务队列中取出下一个宏任务执行,如此循环往复。
示例
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
Promise.resolve().then(() => {
console.log('3');
});
console.log('4');
// 输出顺序: 1 -> 4 -> 3 -> 2
// 解释:
// 1 和 4 同步执行,立即输出。
// setTimeout 被推入宏任务队列。
// Promise.then 被推入微任务队列。
// 同步代码执行完毕后,检查微任务队列并执行,输出 3。
// 随后执行宏任务队列中的 setTimeout,输出 2。
总结
JavaScript 的事件循环机制使得 JavaScript 能够在单线程环境中执行异步操作而不会阻塞主线程。通过任务队列和微任务队列,JavaScript 能够高效地管理异步任务,并保证了异步操作的顺序性和响应性。