前言

首先来看一个JavaScript的代码片段:

console.log(1);  setTimeout(() => {   console.log(2);   Promise.resolve().then(() => {     console.log(3)   }); }, 0);  new Promise((resolve, reject) => {   console.log(4)   resolve(5) }).then((data) => {   console.log(data); })  setTimeout(() => {   console.log(6); }, 0)  console.log(7);

如果你能知道正确的答案,那么后续的内容可以略过了;如果不能建议看看下面有关js异步的内容,百利无一害,😁😁。

任务队列

js的一大特点是单线程,即同一个时间只能做一件事,这样设计主要与其作为浏览器脚本语言有关,js主要用途是用户交互以及操作dom,这决定其是单线程设计,否则会带来复杂的同步问题。比如一个线程删除一个节点,而另一个线程要操作该节点,浏览器不知以哪个线程为准。

单线程意味着任务需要排队,如果前一个任务耗时长,那么就会阻塞后续任务的执行。为此js出现了同步和异步任务,二者都需要在主线程执行栈中执行;其中异步任务需要进入任务队列(task queue)进行排队,其具体运行机制如下:

  • 同步任务在主线程上执行,形成一个执行栈

  • js会将主线程执行栈中的异步任务置于任务队列排队

  • 一旦主线程执行栈同步任务执行完毕处于空闲状态时,就会将任务队列中任务入栈开始执行

还是先来看一个js片段:

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

这段代码在进入主线程执行时,当执行到setTimeout时会将其放置到异步任务队列中,即使设置时间为0也不会马上执行,必须等到主线程执行栈空闲时(执行完console.log('script end')语句后)才会读取异步队列的任务执行。

macrotask与microtask

二者任务都会被放置于任务队列中等待某个时机被主线程入栈执行,其实任务队列分为宏任务队列和微任务队列,其中放置的分别为宏任务和微任务。

  • macrotask(宏任务) 在浏览器端,其可以理解为该任务执行完后,在下一个macrotask执行开始前,浏览器可以进行页面渲染。触发macrotask任务的操作包括:

    • scrip(整体代码)

    • setTimeoutsetIntervalsetImmediate(Node环境)

    • I/OUI交互事件

    • postMessageMessageChannel

  • microtask(微任务)可以理解为在macrotask任务执行后,页面渲染前立即执行的任务。触发microtask任务的操作包括:

    • Promise.then

    • MutationObserver

    • process.nextTick(Node环境)

下面通过例子来看看二者的不同:

console.log('script start'); setTimeout(function() {   console.log('timeout'); }, 0); Promise.resolve().then(function() {   console.log('promise1'); }).then(function() {   console.log('promise2'); }); console.log('script end');