题目
倔友们一起看一下把 ~
面试官 : 讲一下事件循环 , 顺便做了一道题🤪:下面字母的输出顺序是什么 ?
js
async function async1() {
console.log('E');
await async2();
console.log('F');
}
async function async2() {
console.log('G');
}
setTimeout(() => console.log('H'), 0);
async1();
new Promise((res) => {
console.log('I');
res();
}).then(() => console.log('J'))
这是一段涉及 JavaScript 中异步操作( async/await 、 Promise 、 setTimeout )的代码,要确定其执行顺序,需要了解这些异步机制的执行原理------ 事件循环机制(Event loop)
答案 : E 、 G 、 I 、 F 、 J 、 H
不清楚的倔友一起往下看 :
事件循环
事件循环是JavaScript中用于
- 处理异步操作
- 协调事件处理
在网页开发中,事件循环确保用户界面的响应性。例如,当用户点击按钮时,点击事件作为一个宏任务被放入任务队列,在合适的时机被处理。在处理网络请求时,发起请求是异步操作,不会阻塞其他代码执行,请求完成后,响应数据的处理函数会作为一个宏任务或微任务被放入任务队列,等待执行。
基本概念
JavaScript是单线程语言,同一时间只能执行一个任务,但可以通过事件循环来实现异步操作。事件循环允许JavaScript在执行同步任务的同时,处理各种异步任务,如网络请求、定时器、用户交互等,不会因为某个耗时的操作而阻塞整个程序的运行。
工作原理
- 执行栈:也叫调用栈,是一种存储函数调用关系的数据结构。当函数被调用时,会被压入执行栈,函数执行完毕后从栈顶弹出。JavaScript引擎会按照顺序执行执行栈中的函数。
- 任务队列 :包括宏任务队列和微任务队列。
- 宏任务包括 setTimeout 、 setInterval 、 setImmediate (Node.js环境)、I/O操作、UI渲染等
- 微任务包括 Promise 的回调、 MutationObserver 等
- 循环机制 :事件循环首先会检查执行栈 是否为空,如果为空,则检查微任务队列 是否有任务。如果有微任务,就会依次执行微任务队列中的所有任务,直到微任务队列为空。然后,事件循环会检查宏任务队列,从宏任务队列中取出一个宏任务放入执行栈执行,执行完这个宏任务后,再次检查微任务队列并执行其中的任务,如此循环往复。
执行流程图
这张图说明了一下几个点 :
- 同步代码先执行
- 异步代码先执行宏任务 , 再执行微任务
这个时候 , 我们再来看这段代码的执行顺序 ,简直是有手就行 ~
首先执行同步代码,遇到 setTimeout(() => console.log('H'), 0); , setTimeout 会将其回调函数放入宏任务队列中,暂不执行。
接着调用 async1() ,进入 async1 函数,首先执行 console.log('E'); ,输出 E 。
然后遇到 await async2(); ,先执行 async2 函数, async2 函数中 console.log('G'); ,输出 G 。此时 await 会暂停 async1 函数的执行,将 async1 函数中 await 后面的代码( console.log('F'); )放入微任务队列中。
继续执行同步代码,遇到 new Promise((res) => { console.log('I'); res(); }).then(() => console.log('J')); , Promise 的构造函数中的 console.log('I'); 会立即执行,输出 I ,并且 Promise 状态变为 fulfilled ,其 then 回调函数会被放入微任务队列中。
此时同步代码执行完毕 ,开始处理微任务队列。先执行 async1 函数中 await 后面被放入微任务队列的 console.log('F'); ,输出 F 。
接着执行 Promise 的 then 回调函数 console.log('J'); ,输出 J 。
微任务队列处理完毕后 ,开始处理宏任务队列,执行 setTimeout 的回调函数 console.log('H'); ,输出 H 。
综上所述,这段代码的执行顺序是: E 、 G 、 I 、 F 、 J 、 H 。
总结
两个点
- 顺序 :同步代码 => 微任务 => 宏任务
- 循环 :宏任务 => 微任务 , 没有微任务继续执行宏任务