面试题答案
一键面试事件循环机制下异步操作执行流程
- 宏任务队列与微任务队列:JavaScript 运行环境有一个事件循环(Event Loop),它会不断检查调用栈是否为空。当调用栈为空时,会从宏任务队列中取出一个任务放入调用栈执行。宏任务包括
setTimeout
、setInterval
、setImmediate
(Node.js 环境)、I/O 操作、UI 渲染等。而微任务队列会在当前宏任务执行完,且调用栈再次为空时,被事件循环处理。微任务包括Promise.then
、process.nextTick
(Node.js 环境)、MutationObserver
等。 setTimeout
执行流程:setTimeout
会将其回调函数放入宏任务队列。当设定的延迟时间到达后,回调函数就会进入宏任务队列等待执行。只有当调用栈为空,事件循环从宏任务队列中取出该任务放入调用栈,才会执行setTimeout
的回调。Promise
执行流程:Promise
构造函数内的代码是同步执行的。当Promise
状态改变(resolve
或reject
)时,.then
或.catch
中的回调函数会被放入微任务队列。在当前宏任务执行完,调用栈为空时,事件循环会处理微任务队列,依次执行这些微任务。async/await
执行流程:async
函数返回一个Promise
。await
关键字只能在async
函数内部使用,它会暂停async
函数的执行,等待Promise
被解决(resolved
或rejected
)。await
后的Promise
解决后,await
表达式的值就是该Promise
的解决值。await
之后的代码会被包装成微任务放入微任务队列。
多个微任务和宏任务嵌套时预测输出结果
- 宏任务优先:首先执行同步代码,同步代码执行完后,事件循环开始处理宏任务队列。每次从宏任务队列取出一个宏任务执行,直到宏任务执行完且调用栈为空。
- 微任务紧跟宏任务:在一个宏任务执行完且调用栈为空后,事件循环会处理微任务队列,将微任务队列中的所有任务依次执行完,才会再次从宏任务队列中取出任务。
- 嵌套情况分析:
- 当遇到多层
setTimeout
嵌套时,它们会按照设定的延迟时间依次进入宏任务队列。较早到期的setTimeout
回调先进入宏任务队列,先被执行。 - 对于
Promise
嵌套,如果内层Promise
的.then
回调中又创建了新的Promise
,新的Promise
的.then
回调依然会被放入微任务队列。但要注意,外层Promise
的.then
回调会在内层Promise
解决后,先于新创建Promise
的.then
回调执行(因为微任务队列是先进先出)。 - 在
async/await
嵌套中,内层async
函数中的await
会暂停内层函数执行,外层async
函数也会暂停,直到内层await
的Promise
解决。内层await
后的代码作为微任务处理,外层await
后的代码同样作为微任务,且外层await
后的微任务会在内层await
后的微任务执行完后执行(因为微任务队列先进先出)。
- 当遇到多层
通过明确宏任务和微任务的执行顺序,以及各种异步操作在事件循环中的处理方式,就可以准确预测最终的输出结果。