【JavaScript 高级】事件循环机制详解
背景
JavaScript 是一种单线程语言,这意味着它在同一时间只能执行一个任务。然而,JavaScript 通过事件循环机制实现了异步编程,使得它可以处理多个任务,而不会阻塞主线程。
JavaScript 的事件循环(Event Loop)是一种运行机制,用于处理异步任务和事件回调。它基于单线程模型,通过任务队列(Task Queue)和微任务队列(Microtask Queue)来调度任务的执行顺序。
核心概念
(1)调用栈
调用栈是一个后进先出(LIFO)的数据结构,用于跟踪函数的调用顺序。当一个函数被调用时,它会被压入调用栈;当函数执行完毕时,它会被弹出调用栈。
function bar() {console.log('bar');
}function foo() {bar();console.log('foo');
}foo();
在这个例子中,调用栈的变化如下:
foo() 被调用,foo 被压入调用栈。
foo() 中调用 bar(),bar 被压入调用栈。
bar() 执行完毕,bar 被弹出调用栈。
foo() 继续执行,foo 执行完毕后被弹出调用栈。
(2)任务队列
任务队列是一个先进先出(FIFO)的数据结构,用于存储异步任务。当一个异步任务完成时,它会被放入任务队列中,等待事件循环将其放入调用栈中执行。
setTimeout(() => {console.log('setTimeout');
}, 0);console.log('start');
在这个例子中,setTimeout 的回调函数会被放入任务队列中,等待事件循环将其放入调用栈中执行。
任务队列包括宏任务队列和微任务队列:
- 宏任务:setTimeout/setInterval、DOM操作、I/O、UI渲染、事件回调等
- 微任务:Promise、MutationObserver、queueMicrotask、process.nextTick(Node.js)。
(3)事件循环
事件循环是一个无限循环,用于监控调用栈和任务队列。当调用栈为空时,事件循环会从任务队列中取出一个任务并将其放入调用栈中执行。
事件循环的执行顺序
- 同步任务:直接进入调用栈执行。
- 微任务:在每次事件循环的末尾执行。
- 宏任务:在事件循环的每个阶段执行。
优先级
同步代码 > 微任务 > 宏任务
注意事项
- 微任务队列必须清空后才会执行下一个宏任务。
- 避免在微任务中递归添加微任务,否则会阻塞事件循环。
- 不同环境(浏览器、Node.js)的事件循环实现可能略有差异。