问:下面代码的宏任务微任务执行顺序是怎样的

163 阅读4分钟

对于事件循环不知道有多少同学在面试的笔试题被它绕的团团转,作为js的处理事件的重要机制,那么今天我们就从最简单的地方开始,把它拿下

了解事件循环机制

为什么会有事件循环机制,js作为一个单线程的语言,意味着如果出现大量事件等它将会按顺序执行,而如果是这样,就会导致如果里面出现一个复杂的计算,就会影响整个进程。就像你在玩游戏,也并不影响我们同时听音乐

用异步去解决局限性,用事件循环机制去调度处理这个过程,分辨哪些是优先处理,哪些是后续处理。确保所有任务按照合理的顺序执行。

事件循环机制内容

简单来说,事件循环机制包括:执行栈宏任务队列微任务队列

将我们的事件任务分为:同步任务,宏任务,微任务

执行过程简单来说:

  1. 将同步任务在执行栈中执行
  2. 宏任务微任务放到特定队列,由事件管理器去调整后续
  3. 同步任务执行完,执行栈为空,事件管理器将微任务放入执行栈执行
  4. 执行栈空,放入宏任务
  5. 继续微任务,宏任务依次类推

常见 宏任务:

  • setTimeout 和 setInterval
  • Ajax请求的回调
  • DOM 事件监听回调
  • postMessage (H5, 向其它窗口分发异步消息) 等

微任务

  • Promise对应的then方法的回调
  • async & await :await语句之后的代码是微任务(await本身不是微任务,只是可以简单一点这么去看)等

案例

多说无益,还是从具体的案例中去了解这个过程吧

image.png

可以看到我们下面的代码的简单案例中,已经标注了每一个代码是什么任务,结合上面的执行过程我们可以看出这个任务的整体输出结果就是 : 1 3 5(同步按顺序执行) 4(微任务) 2(宏任务)

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

        new Promise((resolve) => {
            // 同步
            console.log('3');
            resolve();
        }).then(() => {
            // 微任务
            console.log('4');
        });

        // 同步
        console.log('5');

复杂一些的案例,这次并没有加上每个代码是哪种事件,大家可以自己试试自己加上。结果是 2 3 5 1 4

  1. console.log(2)同步、setTimeout(one, 0)宏任务、promies.then的console.log('3');微任务、定时器console.log('4');宏任务、console.log('5');微任务
  2. 知道每一个代码是什么任务,接下来进行分析得出结果
  3. two()执行,输出2.后面没有同步代码了
  4. 执行微任务, 3 5
  5. 执行宏任务, 1 4
function one() {
  console.log('1');
}

function two() {
  console.log('2');
  setTimeout(one, 0);
  Promise.resolve().then(() => {
    console.log('3');
  });
}

two();

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

Promise.resolve().then(() => {
  console.log('5');
});

加入await机制

接下来加入 await机制,来了解await在机制中的一个效果

解释:await关键字的核心功能是暂停async函数的执行。当 JavaScript 引擎遇到await关键字时,它会暂停当前async函数的执行,将执行权交出去,让其他同步任务或者已经准备好的微任务先执行,这也就是为啥我说await之后可以简单理解为一个微任务,有相似之处,但不完全一样。

案例:

  1. 调用 test 函数,先执行two函数的同步,console.log('6')
  1. 然后执行 return Promise,then 回调函数添加到微任务队列(test 函数里的 await 在等待 two 函数返回的这个 Promise,所以此时 await 会暂停 test 函数的执行,并将这个 Promise 的 then 回调函数(也就是输出 1 的那个微任务)再次添加到微任务队列(这种重复添加不影响最终执行顺序,只是确保这个微任务会在合适的时候执行)。)
  1. 执行 new Promise,同步代码 console.log('3');,输出 3
  2. 调用 resolve,Promise 的 then 回调函数Promise 的 then 回调函数添加到微任务队列
  3. 执行同步代码 console.log('5');,输出 5
  4. 开始处理微任务队列,执行的是 two 函数返回的 Promise 的 then 回调函数,输出 1
  5. 执行 new Promise 的 then 回调函数,输出 4
  6. test 函数恢复执行阶段,执行 await 后面的代码,输出 2
async function test() {
    await two();
    console.log(2);
}
function two() {
    // 同步
   console.log('6');

    // 微任务
    return  Promise.resolve().then(() => {
    console.log('1');
  });
}


test();
new Promise((resolve) => {
    console.log(3);
    resolve();
}).then(() => {
    console.log(4);
});
console.log(5);

练练手

image.png

async function one() {
  console.log('1');
  await two();
  console.log('2');
}
async function two() {
  console.log('3');
return Promise.resolve().then(() => {
    console.log('4');
  });
}
console.log('5');

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

one();

new Promise((resolve) => {
  console.log('7');
  resolve();
}).then(() => {
  console.log('8');
});

console.log('9');

答案:
 5 1 3 7 9 4 8 2 6
const p1 = new Promise((resolve) => {
  console.log('1');
  resolve();
});

const p2 = p1.then(() => {
  console.log('2');
  return new Promise((resolve) => {
    console.log('3');
    resolve();
  });
});

p2.then(() => {
  console.log('4');
});

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

console.log('6');


答案:1 6 2 3 4 5