javascript的宏任务和微任务,js中宏任务和微任务

大家好,给大家分享一下javascript的宏任务和微任务,很多人还不知道这一点。下面详细解释一下。现在让我们来看看!

最开始接触到 js 的微任务与宏任务是在一次面试中,完全懵逼,下面抛出面试题,大家先睹为快:

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

为了搞清楚这个到底是怎么回事,找了好久都没有找到心仪的答案,现在把我查询的结果整理如下,下面我们从根本上依次来讲解JS 的微任务与宏任务

JS 的执行机制

java是一门单线程语言,在最新的HTML5中提出了Web-Worker,但java是单线程这一核心仍未改变。所以一切java版的"多线程"都是用单线程模拟出来的GPT改写

多线程: 程序可以同一时间做几件事。
单线程: 程序同一时间只能做一件事。

1、JS为什么是单线程的?

JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
  
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

2、JS为什么需要异步?

如果JS中不存在异步,只能自上而下执行,如果上一行解析时间很长,那么下面的代码就会被阻塞。 对于用户而言,阻塞就意味着"卡死",这样就导致了很差的用户体验。

3、JS单线程又是如何实现异步的呢?

既然JS是单线程的,只能在一条线程上执行,又是如何实现的异步呢?
  
是通过的事件循环(event loop),理解了event loop机制,就理解了JS的执行机制。理解事件循环机制前,我们先来看看任务队列。

任务队列

“任务队列"是一个事件的队列(也可以理解成消息的队列),IO设备完成一项任务,就在"任务队列"中添加一个事件,表示相关的异步任务可以进入"执行栈"了。主线程读取"任务队列”,就是读取里面有哪些事件。
 
“任务队列"中的事件,除了IO设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入"任务队列”,等待主线程读取。

"任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,"任务队列"上第一位的事件就自动进入主线程。但是,由于存在后文提到的"定时器"功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。

读取到一个异步任务,首先是将异步任务放进事件表格(Event table)中,当放进事件表格中的异步任务完成某种事情或者说达成某些条件(如setTimeout事件到了,鼠标点击了,数据文件获取到了)之后,才将这些异步任务推入事件队列(Event Queue)中,这时候的异步任务才是执行栈中空闲的时候才能读取到的异步任务。

4、java的执行机制

主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
Event Loop 就是 java的执行机制

同步任务与异步任务

1、单线程的不足

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务, 简单理解就是排好队一个一个来。如果前一个任务耗时很长,后一个任务就不得不一直等着。如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。

2、同步和异步解决单线程的不足

JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。

于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。

同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入Event Queue。主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。上述过程会不断重复,也就是常说的Event Loop(事件循环)。

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
在这里插入图片描述

宏任务和微任务

接下来就到了我们要讲的宏任务和微任务,它们都属于异步任务。
主要区别在于他们的执行顺序,Event Loop的走向和取值。

1、宏任务(macro-task)

宏任务是指宿主发起的任务(浏览器/node)。
包括:

  • 同步 (整体代码)
  • 定时器
  • 事件绑定
  • ajax
  • 回调函数
  • Node中fs可以进行异步的I/O操作
  • UI rendering

2、微任务(micro-task)

微任务是指 js 引擎发起的任务。
包括:

  • process.nextTick:node中实现的api,把当前任务放到主栈最后执行,当主栈执行完,先执行nextTick,再到等待队列中找
  • Promise(async/await) :Promise并不是完全的同步,在promise中是同步任务,执行resolve或者reject回调的时候,此时是异步操作,会先将then/catch等放到微任务队列。当主栈完成后,才会再去调用resolve/reject方法执行
  • MutationObserver:创建并返回一个新的 MutationObserver 它会在指定的DOM发生变化时被调用。

3、机制:微任务大于宏任务

js异步有一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入eventqueue,然后在执行微任务,将微任务放入eventqueue最骚的是,这两个queue不是一个queue。当你往外拿的时候先从微任务里拿这个回调函数,然后再从宏任务的queue上拿宏任务的回调函数。 我当时看到这我就服了还有这种骚操作。
在这里插入图片描述

应用

现在我们返回查看文章开始列出的面试题,它的执行顺序是这样的:

1、首先 JavaScript 引擎会执行一个宏任务,注意这个宏任务一般是指主干代码本身,也就是目前的同步代码
2、执行过程中如果遇到微任务,就把它添加到微任务任务队列中
3、宏任务执行完成后,立即执行当前微任务队列中的微任务,直到微任务队列被清空
4、微任务执行完成后,开始执行下一个宏任务
如此循环往复,直到宏任务和微任务被清空。

具体到这道题目,执行顺序是:

首先执行一个宏任务,即所有同步代码,依次打印出:
start
promise1
promise2
end
第一步宏任务执行完成,检查微任务队列,发现微任务队列中有:Promise的then回调函数,立即执行微任务队列。打印出:promise then

然后执行接下来的宏任务,执行setTimeout的回调函数,打印出:setTimeout。

此题还需要注意:setTimeout和setInterval的回调函数是宏任务,但是题目中的setTimeout和setInterval本身是属于主干代码的;

同理打印promise1和promise2的那两行虽然处于 Promise 的回调函数中,但是这个函数的立即同步执行的,所以也属于主干代码。

JavaScript 中,**任务(Macrotask)****任务(Microtask)**是事件循环(Event Loop)机制中的两个核心概念。它们决定了异步代码的执行顺序。 --- ## 🧠 一、基本定义 | 类型 | 示例 | 执行时机 | |------------|----------------------------------|----------| | 任务 | `setTimeout`, `setInterval`, I/O, `requestAnimationFrame` | 每次事件循环的一个完整周期 | | 任务 | `Promise.then/catch/finally`, `queueMicrotask`, `MutationObserver` | 当前任务结束后立即执行 | --- ## 🔁 二、事件循环流程图简述 1. 执行一个任务(如主程序、`setTimeout` 回调) 2. 执行所有当前已排队的任务(清空任务队列) 3. 渲染页面(如有需要) 4. 开始下一轮事件循环,执行下一个任务 --- ## ✅ 三、区别总结 | 特性 | 任务 | 任务 | |------------------|-----------------------------------|-------------------------------------| | 触发方式 | 异步 API(如 `setTimeout`) | Promise 回调、`queueMicrotask` 等 | | 执行优先级 | 较低 | 更高(在下一个任务之前执行完) | | 队列数量 | 多个(每个任务来自不同队列) | 只有一个任务队列 | | 是否阻塞主线程 | 是(长时间任务会卡住页面) | 否(但大量任务仍会影响性能) | --- ## 📌 四、示例说明 ### 示例 1:基础对比 ```javascript console.log("Start"); setTimeout(() => { console.log("Timeout"); // 任务 }, 0); Promise.resolve().then(() => { console.log("Promise then"); // 任务 }); console.log("End"); ``` ### 输出结果: ``` Start End Promise then Timeout ``` > 即使 `setTimeout` 延迟为 0,它仍然是任务,会在所有任务执行完毕后才执行。 --- ### 示例 2:多个任务 vs 任务 ```javascript console.log("Start"); setTimeout(() => { console.log("Timeout 1"); }, 0); Promise.resolve().then(() => { console.log("Promise 1"); Promise.resolve().then(() => { console.log("Nested promise"); }); }); setTimeout(() => { console.log("Timeout 2"); }, 0); ``` ### 输出结果: ``` Start Promise 1 Nested promise Timeout 1 Timeout 2 ``` > 所有任务都会在下一个任务之前执行完成(包括嵌套的任务)。 --- ## ⚠️ 五、注意事项 - 任务具有更高的优先级,过多嵌套或递归任务可能导致页面渲染“卡顿”。 - 不要滥用 `queueMicrotask` 或嵌套 `Promise.then()` 来做高频异步操作。 - 任务适用于:状态更新、DOM 更新后的回调、链式异步逻辑等。 --- ## 🧩 六、如何手动创建任务? ### 方法一:使用 `Promise.then()` ```javascript Promise.resolve().then(() => { console.log("This is a microtask"); }); ``` ### 方法二:使用 `queueMicrotask()` ```javascript queueMicrotask(() => { console.log("Also a microtask"); }); ``` > `queueMicrotask()` 的可读性更好,且不会触发 Promise 的错误处理机制。 --- ## ✅ 总结一句话: > **任务会在当前任务执行完成后立即执行,即使它是刚刚被加入队列的;而任务必须等待整个任务队列清空后才会执行。** --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值