[面试题]事件循环经典面试题解析

基础概念

  1. 进程是计算机已经运行的程序,线程是操作系统能够进行运算调度的最小单位,它被包含在进程中.浏览器中每开一个Tab页,就会打开一个进程,而这个进程又包含了很多线程.
  2. 大家都知道JS是一门单线程语言,如果遇到了非常耗时的操作,那么JS的执行就会受到阻塞,这肯定不是我们想看到的,所以这些耗时的操作,往往不是由JS线程所执行的,而是交由浏览器中的其他线程去完成的,成功之后只要在某个特定的时候进行一个回调函数即可
  3. 所以引出了事件循环的概念,在事件循环中,分两种任务,分别是宏任务和微任务
    1. 宏任务包含 ajax、setTimeout、setInterval、DOM监听、UI Rendering
    2. 微任务包含 Promise的then回调、 Mutation Observer API、queueMicrotask()等
  4. 接下来我们直接就开始练习面试题熟悉熟悉

面试题一

setTimeout(function () {
    console.log("setTimeout1");

    new Promise(function (resolve) {
        resolve();
    }).then(function () {
        new Promise(function (resolve) {
            resolve();
        }).then(function () {
            console.log("then4");
        });
        console.log("then2");
    });
});

new Promise(function (resolve) {
    console.log("promise1");
    resolve();
}).then(function () {
    console.log("then1");
});

setTimeout(function () {
    console.log("setTimeout2");
});

console.log(2);

queueMicrotask(() => {
    console.log("queueMicrotask1")
});

new Promise(function (resolve) {
    resolve();
}).then(function () {
    console.log("then3");
});
  1. 先解决同步任务
    1. 输出promise1 2
  2. 开始解决异步任务中的微任务
    1. 输出then1 queueMicrotask1 then3
  3. 开始解决异步任务中的宏任务
    1. 输出setTimeout1,在第一个定时器中,又遇到了微任务,那么接着执行微任务
      1. 输出then2 然后输出 then4
    2. 目光跳出第一个定时器中,看到第二个定时器 开始输出setTimeout2
  4. 最后的完整输出为 promise1 2 then1 queueMicrotask1 then3 setTimeout1 then2 then4 setTimeout2

面试题二

async function async1() {
  console.log('async1 start')
  // await异步函数的返回结果 resolve的结果会作为整个异步函数的promise的resolve结果->同步代码
  // await后面的执行代码 就会变成.then后面的执行函数->微任务
  // 也就是说  console.log('async1 end') 这一段是相当于then方法内的 会被加入微任务中
  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')
})

console.log('script end')
  1. 先执行同步代码
    1. 输出script start async1 start async2 promise1 script end
  2. 开始执行微任务
    1. 输出async1 end promise2
  3. 最后执行宏任务
    1. 输出setTimeout
  4. 完整输出:script start async1 start async2 promise1 script end async1 end promise2 setTimeout

面试题三

Promise.resolve().then(() => {
  console.log(0);
  //1.直接返回4 微任务不会做任何延迟 
  // return 4
  //2.直接返回Promise.resolve(4) 微任务推迟两次
  // return Promise.resolve(4);
  //3.返回thenable对象
  return {
    then: ((resolve, reject) => {
      resolve(4);
    })
  }
}).then((res) => {
  console.log(res)
})

Promise.resolve().then(() => {
  console.log(1);
}).then(() => {
  console.log(2);
}).then(() => {
  console.log(3);
}).then(() => {
  console.log(5);
}).then(() => {
  console.log(6);
})

这道面试题有些特殊,需要大家记住两个结论

  1. 如果返回的是thenable对象,那么微任务会推迟一次,thenable对象就是实现了Promise.then的那个函数,具体可看代码
  2. 如果返回的是Promise.resolve(4),那么微任务会推迟两次,这个相当于是返回一个Promise之后又用了resolve,二者是等价的
  3. 为了配合大家理解,我给大家画了几张图,大家可以看看

image.png
事件循环_return.gif事件循环_return_thenable_.gif事件循环_return_promise.resolve_.gif

面试题四

本道题是基于node的事件循环,和浏览器的事件循环不一样,需要记住以下几点

node的事件循环也分宏任务和微任务

  • 宏任务: setTimeout、setInterval、IO事件、setImmediate、close事件
  • 微任务: Promise的then回调、process.nextTick、queueMicrotask

node的每次事件循环都是按照以下顺序来执行的

  1. next tick microtask queue
  2. other microtask queue
  3. timer queue
  4. poll queue
  5. pcheck queue
  6. close queue
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('setTimeout0')
}, 0)

setTimeout(function () {
  console.log('setTimeout2')
}, 300)

setImmediate(() => console.log('setImmediate'));

process.nextTick(() => console.log('nextTick1'));

async1();

process.nextTick(() => console.log('nextTick2'));

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

console.log('script end')
  1. 首先执行同步任务
    1. 输出script start async1 start async2 promise1 promise2 script end
  2. 接着执行微任务,因为node会优先执行nextTick这个微任务
    1. 所以先输出nextTick1 nextTick2
    2. 在输出其他微任务,输出async1 end promise3
  3. 最后执行宏任务
    1. 输出 setTimeout0 setImmediate
    2. 因为这个定时器延时3ms执行,所以会让其他的宏任务先执行完毕,才回去执行这个定时器,所以最后输出setTimeout2
  4. 最后的输出结果: script start async1 start async2 promise1 promise2 script end nextTick1 nextTick2 async1 end promise3 setTimeout0 setImmediate setTimeout2