面试中常见的问题:JavaScript 宏任务与微任务,包教包会

发布于:2025-07-08 ⋅ 阅读:(31) ⋅ 点赞:(0)

事件循环Event Loop

我们都知道,JavaScript 是一种单线程的编程语言,简单的说就是:js只有一条通道,那么在任务多的情况下,就会出现拥挤的情况,这种情况下就产生了 ‘多线程’ ,但是这种“多线程”是通过单线程模仿的,也就是假的。

这意味着它一次只能执行一个任务。但是,通过事件循环(Event Loop)机制,可以处理异步操作并确保程序的高效运行。理解事件循环对于我们前端开发者来说至关重要!

事件循环的基本概念

事件循环的核心在于任务队列(Task Queue),它负责管理和调度各种任务的执行。任务分为两类:宏任务微任务

事件循环的执行步骤

事件循环的基本执行步骤如下:

  1. 执行一个宏任务(例如,从头到尾运行一个 script)。
  2. 执行所有的微任务(一直执行,直到微任务队列为空)。
  3. 更新渲染(如果需要)。
  4. 继续执行下一个宏任务。

看不懂没关系,结合下面的例子就能明白了!

宏任务和微任务详解

基于上面的基础知识,我们详细介绍下宏任务和微任务

为什么要区分宏任务和微任务?

(1)js是单线程的,但是分同步异步
(2)微任务和宏任务皆为异步任务,它们都属于一个队列
(3)宏任务一般是:script、setTimeout、setInterval、postMessage、MessageChannel、setImmediate(Node.js 环境)
(4)微任务:Promise.then、Object.observe、MutationObserver、process.nextTick(Node.js 环境)
(5)先执行同步再执行异步,异步遇到微任务,先执行微任务,执行完后如果没有微任务,就执行下一个宏任务,如果有微任务,就按顺序一个一个执行微任务

宏任务、微任务有哪些?

  • 宏任务一般是:script、setTimeout、setInterval、postMessage、MessageChannel、setImmediate(Node.js 环境)
  • 微任务:Promise.then、Object.observe、MutationObserver、process.nextTick(Node.js 环境)

宏任务、微任务是怎么执行的?

简单来说,宏任务、微任务的执行顺序是:先执行同步代码,异步任务放入任务队列中,当所有同步代码执行完毕后,先执行微任务在执行宏任务。

接下来,我们将通过很多例子,帮助大家深入了解

基本事件循环

setTimeout(function(){
    console.log(1);
});
new Promise(function(resolve){		    
    console.log(2);
    resolve();
}).then(function(){		    
    console.log(3);
}).then(function(){
    console.log(4)
}); 		
console.log(5);
// 2 5 3 4 1

分析

  1. setTimout是异步宏任务,放入宏任务队列中
  2. new Promise在实例化的过程中所执行的代码都是同步进行的,所以输出2
  3. Promise.then是异步微任务,将其放入微任务队列中
  4. 遇到同步任务console.log(5);输出5;主线程中同步任务执行完
  5. 从微任务队列中取出任务到主线程中,输出3、 4,微任务队列为空
  6. 从宏任务队列中取出任务到主线程中,输出1,宏任务队列为空
console.log(1)
setTimeout(function() {
    console.log(2)
}, 0)
const p = new Promise((resolve, reject) => {
    resolve(4)
})
p.then(data => {
    console.log(data)
})
console.log(3)
//1,3,4,2
  • 遇到同步任务console.log(1);输出1;
  • 遇到setTimeout 异步宏任务,放入宏任务队列中;
  • 遇到 Promise,new Promise在实例化的过程中所执行的代码都是同步进行的,但由于new Promise没有输出事件,所以接着执行遇到.then;
  • 执行.then,异步微任务,被分发到微任务Event Queue中;
  • 遇到同步任务console.log(3);输出3;
  • 主线程中同步任务执行完,从微任务队列中取出任务到主线程中,p.then 输出4,微任务执行完毕,任务队列为空;
  • 开始执行宏任务setTimeout 输出2,宏任务队列为空;

嵌套的宏任务与微任务

console.log('脚本开始')
setTimeout(() => {
    console.log('这是setTimeout1-------------');
    Promise.resolve().then(() => {
        console.log('这是promise1');
    });
}, 0);

setTimeout(() => {
    console.log('这是setTimeout2');
}, 0);

Promise.resolve().then(() => {
    console.log('这是promise2');
});

console.log('脚本结束');

执行顺序:

  1. console.log('脚本开始') —— 同步代码,立即执行。
  2. 第一个 setTimeout 回调注册到宏任务队列。
  3. 第二个 setTimeout 回调注册到宏任务队列。
  4. Promise 回调注册到微任务队列。
  5. console.log('脚本结束') —— 同步代码,立即执行。
  6. 当前宏任务执行完毕,执行微任务队列:
    • console.log('这是promise2')
  7. 微任务执行完毕,执行第一个宏任务队列:
    • console.log('这是setTimeout1-------------')
    • 注册新的微任务 console.log('这是promise1') 到微任务队列。
  8. 执行微任务队列:
    • console.log('这是promise1')
  9. 执行第二个宏任务队列:
    • console.log('这是setTimeout2')

输出结果:

脚本开始
脚本结束
这是promise2
这是setTimeout1-------------
这是promise1
这是setTimeout2

多个微任务

console.log('脚本开始');

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

Promise.resolve().then(() => {
    console.log('这是promise1');
}).then(() => {
    console.log('这是promise2');
}).then(() => {
    console.log('这是promise3');
});

console.log('脚本结束');

执行顺序:

  1. console.log('脚本开始') —— 同步代码,立即执行。
  2. setTimeout 回调注册到宏任务队列。
  3. Promise 回调注册到微任务队列。
  4. console.log('脚本结束') —— 同步代码,立即执行。
  5. 当前宏任务执行完毕,执行微任务队列:
    • console.log('这是promise1')
    • 注册新的微任务 console.log('这是promise2') 到微任务队列。
    • 执行微任务队列:
      • console.log('这是promise2')
      • 注册新的微任务 console.log('这是promise3') 到微任务队列。
      • 执行微任务队列:
        • console.log('这是promise3')
  6. 微任务执行完毕,执行宏任务队列:
    • console.log('这是setTimeout')

输出结果:

脚本开始
脚本结束
这是promise1
这是promise2
这是promise3
这是setTimeout

总结

通过这些详细的例子,相信大家对宏任务与微任务一定有了更深入的了解。希望本文对大家有帮助~感谢支持


网站公告

今日签到

点亮在社区的每一天
去签到