宏任务与微任务傻傻分不清?看这篇文章就够了

在JavaScript的异步编程模型中,宏任务(MacroTask)和微任务(MicroTask)是两个核心概念。它们共同构成了JavaScript的事件循环(Event Loop),使得JavaScript能够非阻塞地执行异步操作。了解宏任务和微任务的工作原理,对于编写高效、可维护的异步代码至关重要。

一、宏任务(MacroTask)

宏任务是由宿主环境(例如浏览器或Node.js)提供的任务,通常包括:

  1. 整体代码执行(script) :在浏览器加载页面时,首先会执行整体的JavaScript代码。这个执行过程本身就是一个宏任务。
  2. setTimeout 和 setInterval:这两个是JavaScript中常用的定时器函数,用于在指定的时间后执行某个函数,或者每隔一段时间执行某个函数。当定时器时间到达时,会触发一个宏任务,将回调函数放入宏任务队列中等待执行。
  3. setImmediate(Node.js特有) :在Node.js环境中,setImmediate()函数用于在I/O事件完成后,但在其他宏任务(如setTimeout)之前执行回调函数。因此,它也可以被视为一个宏任务。
  4. I/O 操作:输入/输出操作,如读取文件、网络请求等,通常也是宏任务。当I/O操作完成时,会触发相应的回调函数,并将这些回调函数作为宏任务放入任务队列中。
  5. UI渲染:在浏览器中,当页面需要重绘或重排时,也会触发宏任务。这些任务通常与页面的渲染性能相关。
  6. 事件回调:包括DOM事件(如点击、滚动等)和Web API事件(如XMLHttpRequest完成等)。当这些事件发生时,会触发相应的回调函数,并将这些回调函数作为宏任务放入任务队列中。

当JavaScript引擎开始执行代码时,会首先执行同步代码,然后将异步代码(如setTimeout、setInterval等)放入宏任务队列中等待执行。

二、微任务(MicroTask)

微任务是由JavaScript引擎自己维护的任务队列:

  1. Promise 的回调:在 JavaScript 中,Promise 用于处理异步操作。当 Promise 的状态从 pending 变为 resolved 或 rejected 时,会执行相应的回调函数,这些回调函数就可以被视为微任务。
  2. async/await:这是基于 Promise 的语法糖,用于简化异步代码的书写。当使用 async/await 编写的异步函数执行完毕后,其后续的代码(包括 then 或 catch 中的回调函数)也会作为微任务执行。
  3. MutationObserver:在浏览器中,MutationObserver 用于监听 DOM 树的变化。当监听到变化时,会触发回调函数,这些回调函数也是微任务。
  4. process.nextTick(Node.js 独有):在 Node.js 中,process.nextTick() 方法用于将回调函数放在当前执行栈的末尾,即在当前同步任务执行完毕后立即执行,它的优先级比 Promise 的回调要高。
  5. Object.observe(已废弃):虽然 Object.observe 方法已被废弃,并被 Proxy 对象替代,但在它存在的时候,用于监听对象属性的变化,并在变化时触发回调函数,这些回调函数也是微任务。

当JavaScript引擎执行完一个宏任务后,会检查微任务队列是否有待执行的微任务,如果有,则清空微任务队列中的所有任务,然后再执行下一个宏任务。

三、宏任务与微任务的执行顺序

JavaScript引擎在执行异步代码时,会按照以下顺序进行处理:

  1. 执行一个宏任务(通常是整体代码)。
  2. 执行完宏任务后,检查并执行所有微任务。
  3. 重复上述步骤,直到宏任务队列和微任务队列都为空。

四、示例

下面是一个简单的示例,演示了宏任务微任务的执行顺序:

例子1

思考下,以下会输出什么

javascript 复制代码
console.log('script start'); // 宏任务  
  
setTimeout(function() {  
  console.log('setTimeout'); // 宏任务  
}, 0);  
  
Promise.resolve().then(function() {  
  console.log('promise1'); // 微任务  
}).then(function() {  
  console.log('promise2'); // 微任务  
});  
  
console.log('script end'); // 宏任务

输出结果:

arduino 复制代码
script start  
script end  
promise1  
promise2  
setTimeout

是否跟你想的一致呢?接下来我们解析一下吧,其实聪明的你看到注释应该已经明白一切了。

  1. 首先执行同步代码,输出"script start"。
  2. 然后遇到setTimeout,将其回调函数放入宏任务队列。
  3. 接着遇到Promise的then方法,将其回调函数放入微任务队列。
  4. 继续执行同步代码,输出"script end"。
  5. 此时,一个宏任务执行完毕,JavaScript引擎开始执行微任务队列中的所有任务,输出"promise1"和"promise2"。
  6. 微任务队列为空后,执行下一个宏任务(setTimeout的回调函数),输出"setTimeout"。

例子2

这次没注释了喔,再来猜猜看会输出什么

javascript 复制代码
console.log('script start')
let promise1 = new Promise(function (resolve) {
    console.log('promise1')
    resolve()
    console.log('promise1 end')
}).then(function () {
    console.log('promise2')
})
setTimeout(function(){
    console.log('settimeout')
})
console.log('script end')

输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout 这次的你是否跟答案想的一致呢,如果一致那么恭喜你,你已经掌握区分宏任务微任务的能力了,接下来我们剖析下代码的执行顺序。

  1. 首先执行同步代码,输出"script start"。
  2. 然后遇到Promise,执行里面的回调,输出"promise1";"promise1 end"。
  3. 接着遇到Promise的then方法,将其回调函数放入微任务队列。
  4. 继续执行同步代码,然后遇到setTimeout,将其回调函数放入宏任务队列。
  5. 继续执行同步代码,输出"script end"。
  6. 此时,一个宏任务执行完毕,JavaScript引擎开始执行微任务队列中的所有任务,输出"promise2"。
  7. 任务队列为空后,执行下一个宏任务(setTimeout的回调函数),输出"setTimeout"。

这个例子跟上个例子对比只是多了一步Promise的同步执行函数,很多人会把promise的同步执行函数跟.then() 微任务混淆,要记住Promise本身是**同步的立即执行函数**, 当在executor中执行resolve或者reject的时候, 此时是异步操作

例子3

来个难的,猜猜以下会输出什么

javascript 复制代码
const first = () => (new Promise((resolve, reject) => {
    console.log(3);
    let p = new Promise((resolve, reject) => {
        console.log(7);
        setTimeout(() => {
            console.log(5);
            resolve(6);
            console.log(p)
        }, 0)
        resolve(1);
    });
    resolve(2);
    p.then((arg) => {
        console.log(arg);
    });
}));
first().then((arg) => {
    console.log(arg);
});
console.log(4);

头昏眼花了吧,老弟。公布答案:3->7->4->1->2->5->8, 哈哈哈,是否和你想的一致呢?如果是,那么恭喜你,已经掌握了事件循环的机制了。如果不是,也不要气馁,多看几遍会开窍的,废话不多说,直接上解析:

要理解这段代码的执行顺序,我们需要首先明白几个关键点:

  1. Promise的构造函数是立即执行的,它里面的代码会同步运行。
  2. setTimeout是一个异步操作,它的回调函数会在当前执行栈清空后被放入事件队列,等待下一次事件循环执行。
  3. Promise的.then()方法中的回调函数是异步执行的,当Promise的状态变为fulfilled(即resolved)时,这些回调函数会被放入微任务队列,等待当前执行栈清空后执行。

基于以上几点,我们可以分析代码的执行顺序:

  1. 首先同步执行Promise构造函数的代码,输出"3"。
  2. 接着执行内部Promise的构造函数,输出"7"。
  3. 同步执行console.log(4),输出"4"。
  4. 设置一个setTimeout,回调会被放入事件队列。
  5. 内部Promise的resolve立即执行,状态变为fulfilled。
  6. 外部Promise的resolve立即执行,状态变为fulfilled
  7. 添加内部Promise的.then()到微任务队列。
  8. 添加外部Promise的.then()到微任务队列。
  9. 执行微任务队列中的任务,输出"1"(内部Promise的resolve值)。
  10. 执行微任务队列中的任务,输出"2"(外部Promise的resolve值。
  11. 当微任务执行完成之后,执行事件队列中的宏任务,输出"5"。
  12. 这里的resolve(6)不会影响外部Promise的状态。
  13. 最后输出"8"。

通过这些示例,我们可以看到宏任务和微任务在JavaScript事件循环中的执行顺序。了解这一点,可以帮助我们更好地编写和管理异步代码,避免潜在的问题和错误。

在这里我斗胆出两道题目考考大家,知道的把答案打在评论区吧:

题目1

提示:注意定时器的延迟时间

javascript 复制代码
const async1 = async () => {
  console.log('async1');
  setTimeout(() => {
    console.log('timer1')
  }, 2000)
  await new Promise(resolve => {
    console.log('promise1')
  })
  console.log('async1 end')
  return 'async1 success'
} 
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .catch(4)
  .then(res => console.log(res))
setTimeout(() => {
  console.log('timer2')
}, 1000)

题目2:

提示:注意宏任务,微任务进出队列顺序

javascript 复制代码
Promise.resolve()  
  .then(() => {  
    console.log('Promise 1');  
    return new Promise(resolve => {  
      setTimeout(() => {  
        console.log('setTimeout 1');  
        resolve();  
      }, 0);  
    });  
  })  
  .then(() => {  
    console.log('Promise 2');  
    Promise.resolve().then(() => console.log('Promise 3'));  
  })  
  .then(() => {  
    console.log('Promise 4');  
  });  
  
setTimeout(() => {  
  console.log('setTimeout 2');  
  Promise.resolve().then(() => console.log('Promise 5'));  
}, 0);  
  
console.log('Script End');

快来试试吧!

相关推荐
崔庆才丨静觅12 分钟前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60611 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了1 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅1 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅2 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
爱敲代码的小鱼2 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax