skFeTeam 本文做者:高扬前端
在前端面试过程当中常常会问到关于代码执行顺序的问题,一般考察的是对浏览器的事件循环、宏任务及微任务的理解深度。掌握这些,对于平常写代码、面试都有帮助。git
首先,咱们先来看一道题 题1:github
(你们能够边看边在内心想出本身以为正确的答案,下面每道题都会带有gif格式图片的题目解析,帮助你们理解~ ps: 图中数字表明代码行数)面试
1 console.log(1);
2 setTimeout(() => {
3 console.log(2);
4 Promise.resolve().then(() => {
5 console.log(3);
6 });
7 }, 0);
8 new Promise((resolve, reject) => {
9 console.log(4);
10 resolve(5);
11 }).then((data) => {
12 console.log(data);
13 });
14 setTimeout(() => {
15 console.log(6);
16 }, 0);
复制代码
答案是:1 4 5 2 3 6promise
这道题其实考察的是对浏览器中事件循环的了解程度。众所周知,浏览器中JS代码单线程执行,全部的同步代码在主线程中执行,造成执行栈。那浏览器中的异步机制是如何实现的呢? 首先,异步任务会被依次放入异步任务队列中,当主线程中的同步任务完成之后,浏览器会轮询去异步任务队列中取出异步任务来执行。浏览器
JS代码中的异步任务可进一步分为宏任务(macrotask)与微任务(microtask)。 宏任务包括:script代码、setTimeout、setInterval、I/O、UI render 微任务包括:promise.then、Object.observe(已废弃)、MutationObserverbash
宏任务和微任务会被加入各自的队列中。 当主线程执行完毕后,浏览器会先去清空微任务队列,依次取出微任务队列中的微任务执行,执行过程当中若是产生新的微任务,则追加到当前微任务队列的末尾等待执行。 当微任务队列清空后,浏览器会从宏任务队列中取出一个宏任务执行。宏任务执行完毕再去清空微任务队列,微任务队列清空后再取出一个宏任务来执行。如此反复,直至宏任务队列和微任务队列所有清空。 异步
题2:async
1 setTimeout(function(){
2 console.log('1')
3 });
4 new Promise(function(resolve){
5 console.log('2');
6 resolve();
7 }).then(function(){
8 console.log('3')
9 });
10 console.log('4');
复制代码
答案:2 4 3 1ui
在分析当前代码时须要注意的是,Promise对象的resolve部分的代码是当前主线程/宏任务的一部分,并不是微任务,Promise对象的then和catch代码段才是微任务。所以最早输出的是2和4,而后才是微任务队列中的3,最后是宏任务中的1。
题3:
1 async function async1() {
2 console.log(1);
3 const result = await async2();
4 console.log(3);
5 }
6 async function async2() {
7 console.log(2);
8 }
9 Promise.resolve().then(() => {
10 console.log(4);
11 });
12 setTimeout(() => {
13 console.log(5);
14 });
15 async1();
16 console.log(6);
复制代码
答案:1 2 6 4 3 5
该题须要注意的是,因为await方法返回的是一个Promise对象,所以await方法执行完毕后续的代码都应该纳入微任务队列,所以**console.log(3)**应该被加入微任务队列等待执行。
题4:
1 function sleep(time) {
2 let startTime = new Date()
3 while (new Date() - startTime < time) {}
4 console.log('1s over')
5 }
复制代码
1 function sleep(time) {
2 let startTime = new Date()
3 while (new Date() - startTime < time) {}
4 console.log('1s over')
5 }
6 setTimeout(() => {
7 console.log('setTimeout - 1')
8 setTimeout(() => {
9 console.log('setTimeout - 1 - 1')
10 sleep(1000)
11 })
12 new Promise(resolve => {
13 console.log('setTimeout - 1 - resolve')
14 resolve()
15 }).then(() => {
16 console.log('setTimeout - 1 - then')
17 new Promise(resolve => resolve()).then(() => {
18 console.log('setTimeout - 1 - then - then')
19 })
20 })
21 sleep(1000)
22 })
23 setTimeout(() => {
24 console.log('setTimeout - 2')
25 setTimeout(() => {
26 console.log('setTimeout - 2 - 1')
27 sleep(1000)
28 })
29 new Promise(resolve => resolve()).then(() => {
30 console.log('setTimeout - 2 - then')
31 new Promise(resolve => resolve()).then(() => {
32 console.log('setTimeout - 2 - then - then')
33 })
34 })
35 sleep(1000)
36 })
复制代码
浏览器输出为:
setTimeout - 1
setTimeout - 1 - resolve
1s over
setTimeout - 1 - then
setTimeout - 1 - then - then
setTimeout - 2
1s over
setTimeout - 2 - then
setTimeout - 2 - then - then
setTimeout - 1 - 1
1s over
setTimeout - 2 - 1
1s over
复制代码
该题须要注意的是微任务执行过程当中产生的新的微任务也是追加在当前微任务队列末尾等待执行。
浏览器中的事件循环机制较为简单,若是将主线程也看做一个宏任务的话,那么浏览器的事件循环机制可看做依次执行如下一、2两点:
while(true) {
宏任务队列.shift();
微任务队列所有任务;
}
复制代码
须要注意的是:
宏任务队列和微任务队列都遵循先进先出原则,先被加入队列的任务优先被取出执行
Promise对象的resolve部分不是微任务,then和catch部分才是,即
new Promise((resolve,reject) => {
console.log('同步');
resolve();
}).then(() => {
console.log('异步');
})
复制代码
微任务执行过程当中产生的新的微任务追加到当前微任务队列队尾等待本轮事件循环执行
await方法返回的是一个Promise对象,所以await方法执行完毕,后续代码都应纳入微任务队列(可结合题4理解)
Node环境的事件循环与浏览器不彻底一致