- 基本概念
- 事件循环是一种机制,用于协调和处理异步任务以及同步任务之间的执行顺序。在JavaScript环境(包括浏览器和Node.js)中,它是处理单线程中各种任务的核心。因为JavaScript是单线程语言,这意味着它一次只能执行一个任务,所以需要一种机制来管理任务的执行顺序,事件循环就是这样的机制。
- 任务队列与执行栈
- 执行栈(Call Stack)
- 执行栈是一个用于存储函数调用的栈结构。当一个函数被调用时,它就会被压入执行栈的顶部,函数执行完成后,它就会从栈顶弹出。这个栈遵循后进先出(LIFO)的原则。例如,在以下代码中:
function a() { console.log('a'); } function b() { a(); } b();
- 首先
b
函数被调用,b
函数被压入执行栈。在b
函数内部调用a
函数,此时a
函数被压入执行栈。当a
函数执行完毕(打印a
)后,a
函数从执行栈弹出,然后b
函数执行完毕也从执行栈弹出。
- 执行栈是一个用于存储函数调用的栈结构。当一个函数被调用时,它就会被压入执行栈的顶部,函数执行完成后,它就会从栈顶弹出。这个栈遵循后进先出(LIFO)的原则。例如,在以下代码中:
- 任务队列(Task Queue)
- 任务队列用于存储异步任务的回调函数。当一个异步操作(如
setTimeout
、Promise
等)完成时,它的回调函数会被放入任务队列。任务队列分为宏任务队列(Macro - task Queue)和微任务队列(Micro - task Queue)。 - 宏任务包括
script
(整体代码)、setTimeout
、setInterval
、I/O
操作、postMessage
、MessageChannel
等。例如,setTimeout
函数会在指定的时间后将回调函数放入宏任务队列。 - 微任务包括
Promise.then
、MutationObserver
等。微任务的优先级高于宏任务。当执行栈为空时,事件循环会先检查微任务队列,如果有微任务,就会将微任务依次放入执行栈执行,直到微任务队列为空。
- 任务队列用于存储异步任务的回调函数。当一个异步操作(如
- 执行栈(Call Stack)
- 事件循环的运行过程
- 当JavaScript代码开始执行时,首先会将全局代码(
script
)作为一个宏任务压入执行栈。在执行全局代码的过程中,可能会产生异步操作。 - 例如,当遇到
setTimeout
函数时,浏览器或Node.js会启动一个定时器,当定时器时间到达后,setTimeout
的回调函数会被放入宏任务队列。而当遇到Promise
对象的then
方法时,then
方法中的回调函数会被放入微任务队列。 - 当执行栈为空时,事件循环会首先检查微任务队列。如果微任务队列中有任务,就会将微任务队列中的任务依次放入执行栈执行。
- 当微任务队列清空后,事件循环会检查宏任务队列,将宏任务队列中的第一个任务放入执行栈执行。当这个宏任务执行完毕后,执行栈再次为空,事件循环会再次检查微任务队列和宏任务队列,如此循环往复,直到所有任务都执行完毕。
- 以以下代码为例:
console.log('start'); setTimeout(() => { console.log('setTimeout'); }, 0); Promise.resolve().then(() => { console.log('Promise.then'); }); console.log('end');
- 首先,
console.log('start')
和console.log('end')
作为全局代码(宏任务)的一部分被执行。然后,setTimeout
的回调函数被放入宏任务队列,Promise.resolve().then
的回调函数被放入微任务队列。当全局代码执行完毕后,执行栈为空,此时事件循环检查微任务队列,发现Promise.then
的回调函数,将其放入执行栈执行,打印Promise.then
。然后微任务队列清空,事件循环检查宏任务队列,将setTimeout
的回调函数放入执行栈执行,打印setTimeout
。
- 当JavaScript代码开始执行时,首先会将全局代码(