前提
在了解JS事件循环机制
之前,先了解两点,JS是一个单线程语言
,JS的事件分类
。
JS是一个单线程语言
单线程就是JS的所有任务都在一条线程
上执行处理。类似于食堂打饭,只有一个窗口,大家都在排一个队。
单线程存在一个弊端
,当某个任务卡住的时候,后面的其他任务就没法执行。或者某个任务耗时很长那么就会导致后面所有的任务都被延迟执行。
在这样的环境下,JS诞生了两个任务种类:同步任务
和异步任务
。
JS的事件分类
JS中的所有任务被分为同步任务
和异步任务
两大类。
同步任务
就是只要被扫描到,就可以被主线程马上执行的任务。(优先于所有异步任务)
类似于console.log(1)
等语句。
异步任务
即使被扫描到,也不会马上执行,异步任务会被压入异步任务队列中,等待主线程中的任务全部清空了,再被召唤执行。
常见的异步任务
有如下几种
Promise.then()
— 微任务async/await
— 微任务setTimeout()
— 宏任务setInterval()
— 宏任务- …(还有更多,不常见的)
举个🌰
setTimeout(() => {
console.log("1"); //同步任务
},0) //异步任务
console.log(2) //同步任务
以上的输出结果是 2 1
。
虽然setTimeout
的延迟是0,但setTimeout
是一个异步任务
,它一定会在所有同步任务
执行完毕之后再去执行。(对应前面同步代码优先于异步任务)
宏任务和微任务
JS
的异步任务又被分为宏任务
和微任务
。
在异步任务
中:
有些异步任务的平均执行周期很长,这些任务被JS
标记为宏任务
(比如setTimeout()
)。
有些异步任务的平均执行周期短的任务,被JS
标记为微任务
(比如promise.then()
)。
而宏任务
和微任务
在执行顺序上是不一样的。具体执行机制如下:
当有异步任务
被压入异步任务队列
时候,JS
会将这些异步任务分为宏任务
和微任务
两个新的队列。然后,在所有同步任务
执行完毕之后,异步任务
会优先执行所有已经存在任务队列中的微任务
。在所有的微任务
执行完毕之后,再去宏任务队列
中执行一个
(注意是一个)宏任务
,执行完一个宏任务
之后会再去微任务队列
中检查是否有新的微任务
,有则全部执行
,再回到宏任务队列
执行一个宏任务
,以此循环。这一套流程,就是事件循环(event loop)
例题
例题一
setTimeout(() => {
console.log("1")
}, 0); //异步任务 - 宏任务
console.log(2); //同步任务
Promise.resolve().then(() => {
console.log(3)
}) //异步任务 - 微任务
console.log(6); //同步任务
输出结果:2 6 3 1
例题二
//第一个宏任务
setTimeout(() => {
console.log(1); //宏任务中的同步任务
Promise.resolve().then(() => { console.log(7) }) //宏任务中的微任务
}, 0); //异步任务 - 宏任务
console.log(2); //同步任务
Promise.resolve().then(() => { console.log(3) }) //异步任务 - 微任务
//第二个宏任务
setTimeout(() => {
console.log(8); //宏任务中的同步任务
setTimeout(() => { console.log(5) }, 0) //宏任务中的宏任务 第四个宏任务
}, 0);
//第三个宏任务
setTimeout(() => {
Promise.resolve().then(() => { console.log(4) }) //宏任务中的微任务
}, 0);
console.log(6); //同步任务
输出结果是:2 6 3 1 7 8 4 5