javascript中宏任务与微任务的区别,各自是怎样界定的?一个例子说明白

抛转引玉

请看下面一个例子,思考一下打印输出顺序是什么?

 console.log('Start');
 setTimeout(function callback() {
  console.log('setTimeout Callback'); 
 }, 0);
 Promise.resolve().then(function callback() {
    console.log('Promise Callback');
 });
 console.log('End');

答案是(文章最后有解释)

Start
End
Promise Callback
setTimeout Callback

你能理解为什么是这个输出顺序吗?如果能,就说明你基本掌握了:

  • 宏任务是什么,微任务是什么
  • 宏任务和微任务的区别
  • 宏任务和微任务的优先级

开始念经

宏任务和微任务以及他们的区别

宏任务和微任务的主要区别在于它们被 JavaScript 引擎处理的时间点。

宏任务包括:

  • setTimeout
  • setInterval
  • requestAnimationFrame
  • UI 渲染(浏览器环境)
  • 脚本所有同步代码的一次执行

微任务包括:

  • Promise.then 或 Promise.catch
  • Vue3中的nextTick
  • MutationObserver(浏览器环境)

宏任务通常来自于 JavaScript 引擎以外的部分,比如 DOM 事件、网络请求、定时器等。微任务通常来自于 JavaScript 代码的执行过程,最常见的是 Promise 的回调。

在 JavaScript 的事件循环中,每一次循环被称为一个宏任务。在每个宏任务中,JavaScript 引擎会首先执行宏任务队列中的一个任务,然后执行完所有的微任务队列。这意味着,如果一个宏任务在执行过程中产生了微任务,那么这些微任务会在下一个宏任务开始之前被执行。

宏任务与微任务的优先级

微任务的优先级高于宏任务。在同一次事件循环中,微任务总是在宏任务之后,但在下一个宏任务之前执行。就是说每当事件循环结束,会优先检查有没有微任务,如果有的话会优先执行微任务。

不懂就问:所有的回调都是微任务吗?

不是!在 JavaScript 中,setTimeout 和 setInterval 的回调函数产生的是宏任务,上文中requestAnimationFrame的回调也是宏任务(有兴趣可以自己查查,是干嘛的哈),而 Promise.then 或 Promise.catch 等异步操作的回调函数产生的是微任务,这是由 JavaScript 的事件循环和任务调度机制决定的。

不懂就问:为什么setTimeout 和 setInterval 的回调函数产生的是宏任务?

setTimeout 和 setInterval 是浏览器提供的 Web API,它们并不是 JavaScript 语言本身的一部分。当你调用 setTimeout 或 setInterval 并传入一个回调函数时,浏览器会启动一个定时器,当定时器到达指定的时间后,浏览器会将回调函数添加到宏任务队列。

宏任务队列是一个先进先出的队列,JavaScript 引擎会在每次事件循环时取出队列中的一个任务并执行。因此,setTimeout 或 setInterval 的回调函数会在下一次事件循环时被执行,这就是为什么它们产生的是宏任务。

不懂就问:微任务是宏任务执行过程中产生的是吗?

不完全是,微任务是可以在宏任务的执行过程中产生的,也可以在其它微任务的执行过程中产生

不懂就问:有可能发生微任务嵌套微任务,永远在执行微任务是吗?

在理论上,确实可以通过在微任务中连续创建新的微任务,从而创建出一连串的微任务。在这种情况下,JavaScript 引擎会在开始新的宏任务之前,先执行完所有已经排队的微任务。这意味着,如果你在一个微任务中不断地创建新的微任务,JavaScript 引擎可能会持续执行微任务,而没有机会开始新的宏任务,可能会导致 JavaScript 引擎耗尽资源,从而影响应用程序的性能。在实践中,我们应该尽量避免创建无限的任务循环,无论是宏任务还是微任务。

举个让浏览器出汗的例子:

//你将永远不会看到setTimeout Callback被打印出来
setTimeout(function callback() {
  console.log('setTimeout Callback'); 
 }, 0);
function recursiveMicrotask() {
  Promise.resolve().then(() => {
    console.log('Microtask executed');
    recursiveMicrotask();
  });
}
recursiveMicrotask();

解释开头的例子

相信经过上面的解释,你应该可以试试自己梳理一下开头的例子,问问自己:

有几个宏任务(有几次事件循环)?

有几个微任务?

输出顺序是啥?

执行过程是怎样的?

答案

以下是代码的执行过程:

  1. 首先,console.log('Start') 会立即执行,因为它是同步代码。

  2. 然后,setTimeout 函数会将其回调函数 callback 添加到宏任务队列,等待执行。尽管 setTimeout 的延迟时间设为 0,但是它的回调函数仍然需要等待当前的同步代码执行完毕,并且等待微任务队列清空后才能执行。

  3. 接下来,Promise.resolve().then 会将其回调函数 callback 添加到微任务队列。

  4. 然后,console.log('End') 会立即执行,因为它是同步代码。

  5. 在当前宏任务的所有同步代码执行完毕后,JavaScript 引擎会开始执行微任务队列中的任务。因此,Promise Callback 会被打印出来。

  6. 在微任务队列清空后,JavaScript 引擎会开始新的事件循环,执行宏任务队列中的任务。因此,setTimeout Callback 会被打印出来。

所以,总的来说,这段代码会发生两次事件循环:一次是执行同步代码和微任务,另一次是执行 setTimeout 的回调函数。

有收获的话可以点个赞哟,激励博主更新更多干货~

  • 33
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值