美文网首页
js中的微任务与宏任务

js中的微任务与宏任务

作者: 之幸甘木 | 来源:发表于2021-03-30 09:49 被阅读0次

导论

先看题目:

question:试着写出下面程序的输出结果:

  console.log(1);
  
  setTimeout(() => {
    console.log(2);
    new Promise((resolve) => {
      console.log(3);
      resolve();
    }).then(res => {
      console.log(4);
    })
    console.log(5);
  }, 0);
  
  new Promise((resolve) => {
    console.log(6);
    resolve();
  }).then(res => {
    console.log(7);
  })
  
  setTimeout(() => {
    console.log(8);
    new Promise((resolve) => {
      console.log(9);
      resolve();
    }).then(res => {
      console.log(10);
    }, 0)
  });
  
  console.log(11);

answer:1 -> 6 -> 11 -> 7 -> 2 -> 3 -> 5 -> 4 -> 8 -> 9 -> 10

宏任务与微任务

在javascript事件循环中,==异步**任务是通过队列(queue)来存储的。流程如下:

javascript任务执行顺序

而异步任务一共分为两种:微任务与宏任务。它们的执行顺序如下:

javascript宏任务与微任务执行顺序

需要注意的是,微任务与宏任务是先注册,再执行。而不是读取到就立即执行。

常见的宏任务(按优先级排列):整体代码script > setImmediate > setTimeout/setInterval

常见的微任务(按优先级排列):process.nextTickNodejs中的内容) > 原生Promise > MutationObserver

回到刚才的那个题目:

  // 主代码块
  console.log(1);
  
  // 注册了一个宏任务
  setTimeout(() => {
    // ...
  }, 0);
  
  // {↓标记↓}
  new Promise((resolve) => {
    // 立即执行部分,其实是同步任务
    console.log(6);
    resolve();
  }).then(res => {
    // 这才是微任务
    console.log(7);
  })
  
  // 注册了一个宏任务
  setTimeout(() => {
    // ...
  });
  // 主代码块
  console.log(11);

所以一开始先执行主代码块,输出1611,请注意,Promise构造函数的参数中的代码是同步任务,与主代码块同步进行。

执行完主代码块后,会寻找微任务队列中的微任务并执行,此时的微任务只有标记的Promise.then()(其他Promsie所在的代码块并未被执行,因此尚未被注册),输出7

微任务执行完后,寻找下一个宏任务 — 第一个SetTimeOut

  setTimeout(() => {
    console.log(2);
    new Promise((resolve) => {
      console.log(3);
      resolve();
    }).then(res => {
      console.log(4);
    })
    console.log(5);
  }, 0);

同理可得:将输出235,并注册一个微任务(Promise.then())。在执行完后执行微任务,输出4

然后再是最后一个宏任务:

 setTimeout(() => {
    console.log(8);
    new Promise((resolve) => {
      console.log(9);
      resolve();
    }).then(res => {
      console.log(10);
    }, 0)
  });

8910,没什么问题了吧,别问,问就同理可得。

西江月·证明
即得易见平凡,仿照上例显然。留作习题答案略,读者自证不难。
反之亦然同理,推论自然成立,略去过程Q.E.D ,由上可知证毕。

其实这个题还差了点意思,换做我就这样出:

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

猜一下答案输出顺序是什么?
...
...
...
...
...
...
...
...
...
答案是 1 -> 6 -> 9 -> 7 -> 2 -> 3 -> 5 -> 4 -> 8。

拓展

常见的宏任务除了上面提到的那些之外,还有一个不曾提到但非常常见的:IO(输入输出流)。你可以简单理解为事件监听(最常见的表现就是事件监听,不过不仅仅包括事件监听)。

上代码:

/* css */
div {
  border: 1px solid black;
}

#outer {
  padding: 25px;
  width: 50px;
  background-color: aqua;
}

#inner {
  width: 50px;
  height: 50px;
  background-color: green;
}
<!--html-->
<div id="outer">
  <div id="inner"></div>
</div>
// js
let $outer = document.getElementById('outer');

let $inner = document.getElementById('inner');

function handler() {
  console.log('click') // 直接输出
  // 注册微任务
  Promise.resolve().then(_ => console.log('promise'))
  // 注册宏任务
  setTimeout(_ => console.log('timeout'))
  // 注册宏任务
  requestAnimationFrame(_ => console.log('animationFrame'));
  // DOM属性修改,触发微任务
  $outer.setAttribute('data-random', Math.random());
}

// 微任务
new MutationObserver(_ => {
  console.log('observer')
}).observe($outer, {
  attributes: true
})

$inner.addEventListener('click', handler);
$outer.addEventListener('click', handler);
效果图

点击div#inner,输出结果: 'click' -> 'promise' -> 'observer' -> 'click' -> 'promise' -> 'observer' -> 'animationFrame' -> 'animationFrame' -> 'timeout' -> 'timeout'。

让我们来捋一下:

点击时通过事件冒泡触发了宏任务$inner.click(),输出'click'。该任务触发了冒泡事件并注册了宏任务$outer.click()。并在事件处理函数handler中注册了两个宏任务(requestAnimationFramesetTimeout)和一个微任务(Promise),并触发了一个微任务MutationObserver

该宏任务执行完后,微任务队列中有两个微任务,按优先级先执行Promise,所以依次输出'promise','observer'。

下一个宏任务,即$outer.click()开始执行,重复了$inner.click()的事件,区别是它没有冒泡并注册新任务了。依次输出'click','promise','observer'。

至此,只剩下几个宏任务对线了。

值得注意的是,因为requestAnimationFrame会触发页面重绘(不是优先级哦),进而会导致setTimeout重置,所以前者会比后者先输出。

后记

祝你学习愉快。

参考文章

  1. 《JS事件循环机制(event loop)之宏任务/微任务》
  2. 《微任务、宏任务与Event-Loop》

相关文章

网友评论

      本文标题:js中的微任务与宏任务

      本文链接:https://www.haomeiwen.com/subject/ddywhltx.html