这是我参与更文挑战的第25天,活动详情查看: 更文挑战
前言
事件循环经常会出现面试官的面试题,因为这个可以考察面试者对执行栈,任务队列,宏任务,微任务等方面的了解程度。我也看了很多文章,但是每次都感觉看的有点囫囵吞枣,或者看完后就忘了,今天借着这次机会,把它复习一遍。
事件循环
首先我们要知道,js是单线程的,就是一个任务执行完,接着下一个任务执行,按照顺序执行。但是如果任务是一个很耗时的任务,那就把线程都堵住了,后面的任务都执行不了。为了防止这种情况:
js把任务分为同步任务
和异步任务
概括起来:先执行同步任务,再执行异步任务。重复如始。
列举一些同步任务:
console.log(1);
console.log(2);
console.log(3);
for(let i = 4; i <= 10; i++) {
console.log(i)
}
function test () {
console.log(11)
}
test()
执行后可以发现从1到11按顺序输出,同步执行。
列举一些异步任务:
setTimeout(() => {
console.log(1)
}, 1000)
Promise.resolve(2).then((i) => {
console.log(i)
})
console.log(3)
可以看到输出顺序是 3 2 1
异步任务
异步任务又分为微任务
和宏任务
。
微任务有这些:
process.nextTick
(Node)Promise
,async
,await
- MutationObserver
宏任务有这些:
- script(整体代码)
- setTimeout
- setInterval
- I/O 操作
- setImmediate(Node)
- requestAnimationFrame
流程
流程是这样的:
- 加载script代码
- 区分同步任务和异步任务
- 主线程先执行同步任务,形成一个执行栈,如果遇到异步任务,区分是宏任务还是微任务,把它的回调压入各自的任务队列
- 主线程执行完同步任务后,检查异步任务队列,先执行微任务,执行完微任务队列再执行宏任务队列;
- 重复上面的过程
微任务先于宏任务执行
下面通过例子来分析下:
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
}).then(function() {
console.log('promise3');
});
console.log('script end');
输出顺序是:
- script start
- async1 start
- async2
- promise1
- script end
- async1 end
- promise2
- promise3
- setTimeout
你做对了吗?
分析:
- 首先先把同步任务先执行, 输出
script start'
- 然后遇到
setTimeout
,把它压入异步宏任务中 - 然后执行
async1
, 输出async1 start
, 接着看到async2
, 先执行async2
, 因为它内部是同步任务,直接输出async2
,因为它async2是await
函数,所以把后面的代码压入异步微任务中 - 然后遇到
Promise
,resolve
之前是同步的,输出promise1
, 把then
部分放到异步微任务中 - 然后遇到同步代码,输出
script end
- 主线程任务都执行完了后,接着执行异步微任务队列,输出
async1 end
,promise2
,promise3
- 微任务队列执行完后,执行宏任务队列,输出
setTimeout
浏览器和Node的事件循环有何区别
浏览器环境和Node环境,微任务任务队列的执行时机不同。
- 浏览器环境,微任务任务队列是在宏任务之中执行;
- Node环境, 微任务任务队列是在事件循环的各个阶段之间执行
总结
以上就是我总结的事件循环(Event Loop),希望对你们理解有所帮助~