一步一步弄懂 JS 事件循环面试题

123 阅读2分钟

对于 JS 事件循环,之前都是看文章有个大致的了解,对于经典的面试题只是看了下,大致知道是什么意思,这次面试碰到了,面试完了仔细看了下文章,当时应该是搞错了,问题中坑还是很多的,用题目的方式记录一下里面的坑。

参考了下面的文章

同步代码

console.log('script start');
console.log('script end');

这个的输出一看便知:

script start
script end

image.png

Promise 的情况

console.log('script start');
new Promise((resolve) => {
    console.log('promise1'); 
    resolve();
}).then(function () { 
    console.log('promise1 then1'); 
}).then(function () { 
    console.log('promise1 then2'); 
});
new Promise((resolve) => {
    console.log('promise2'); 
    resolve();
}).then(function () { 
    console.log('promise2 then1'); 
}).then(function () { 
    console.log('promise2 then2'); 
});
console.log('script end');

关于 Promise 有两个点需要注意:

  • Promise 的构造函数是同步执行的,不是在微任务中执行
  • Promise 的每个 then 都是在上一个 then 执行之后增加到微任务队列中的

具体到上面的代码,promise1 和 promise2 会在 script end 之前打印,而 promise1 then2 会在 promise2 then1 之后打印。

最终的输出如下:

image.png

async/await

console.log("script start");

async function fun1(){
    console.log("fun1 start");
    await Promise.resolve();
    console.log("fun1 end");
}

async function fun2() {
    console.log("fun2 start");
    await fun3();
    console.log("fun2 end");
}

 function fun3(){
    console.log("fun3");
}

fun1();
fun2();

console.log("script end");

async/await 需要注意的点有两个,一是只要是 await 后面的代码都是会放到微任务里面去执行,不管 await 调用的是同步还是异步的代码,二是 await 调用的代码也是同步执行的,执行到异步的代码才会到宏任务或者微任务。

具体到上面的代码,fun1 end 和 fun2 end 都是在微任务中执行,而 fun3 是在同步任务中。

最终的输出如下:

image.png

setTimeout

console.log('script start');
setTimeout(function () {
  console.log('setTimeout');
}, 0);
console.log('script end');

只有 setTimeout 的情况时比较好理解的,setTimeout 的代码会在宏任务中执行,即使定时器时 0 也不是立即执行。

上面的代码执行结果:

image.png

从上面的输出可以看出宏任务和微任务的差异,之前的微任务执行脚本中输出的 undefined 在最后,而在宏任务中,脚本输出 undefined 在 setTimeout 之前。

混合的情况

题目来自下面的文章,文章中有更加详细关于机制方面的解释: juejin.cn/post/686884…

console.log('script start');

setTimeout(() => {
  console.log('setTimeout');
}, 0);

Promise.resolve()
.then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});


async function foo() {
  await bar()
  console.log('async1 end')
}
foo()

async function errorFunc () {
  try {
    await Promise.reject('error!!!')
  } catch(e) {
    console.log(e)
  }
  console.log('async1');
  return Promise.resolve('async1 success')
}
errorFunc().then(res => {
  console.log(res);
})

function bar() {
  console.log('async2 end') 
}

console.log('script end');

从头开始分析代码

// 同步1
console.log('script start');

setTimeout(() => {
  // 宏任务 1
  console.log('setTimeout');
}, 0);

Promise.resolve()
.then(function() {
  // 微任务 1 - 1
  console.log('promise1');
}).then(function() {
  // 微任务 2 - 1
  console.log('promise2');
});


async function foo() {
  await bar()
  // 微任务 1 - 2
  console.log('async1 end')
}
foo()

async function errorFunc () {
  try {
    await Promise.reject('error!!!')
  } catch(e) {
    // 微任务 1 - 3
    console.log(e)
  }
  // 微任务 1 - 4
  console.log('async1');
  return Promise.resolve('async1 success')
}
errorFunc().then(res => {
  // 微任务 2 - 2
  console.log(res);
})

function bar() {
  // 同步 2
  console.log('async2 end') 
}

// 同步 3
console.log('script end');

最终代码的执行结果与分析的结果是一致的: image.png