javaScript 中的宏任务、微任务

宏任务:

是指,需要排队等待 JavaScript 引擎空闲时才能执行的任务,

常见的宏任务包括 setTimeout、setInterval、setImmediate(Node.js 独有)、requestAnimationFrame、I/O 操作、XMLHttpRequest、DOM事件等

微任务:

是指,在当前任务执行结束后,立即执行的任务,它可以看作是在当前任务的"尾巴"添加的任务,

常见的微任务包括 Promise回调函数、process.nextTick、Object.observe(已废弃)、MutationObserver

说一下

JavaScript引擎,会先执行当前任务中的所有微任务,然后再执行宏任务队列中的第一个任务,这个过程会不断重复,直到宏任务队列中的任务被全部执行完毕。

宏任务、微任务都属于异步任务,

宏任务包括 setTimeout、setInterval、I/O 等操作,

微任务包括 promise的then、resolve、reject 等

首先执行同步代码,遇到异步任务,如果是宏任务就放入宏任务队列,微任务就放到微任务队列,

当同步代码执行完毕,就会执行微任务队列,直到微任务队列清空,

然后从宏任务对列调用宏任务到主线程执行,就这样不断循环,直到所有任务执行完毕




JavaScript之所以要区分微任务、宏任务,是因为,微任务和宏任务的执行顺序不同,这对Web开发中一些异步操作的实现有重要的影响

在JavaScript中,微任务会优先于宏任务执行,

这意味着,在当前任务执行结束后,所有微任务都会被立即执行,而宏任务只有在所有微任务执行完毕后才会执行,

这种执行顺序保证了微任务的优先级,可以避免一些问题的出现。

1、

比如,处理 Promise对象时可能会出现的竞态条件,

举个例子,当我们使用Promise对象时,它会返回一个Promise实例并将回调函数放入微任务队列中,

当 Promise的状态发生改变时,它会立即执行微任务队列中的回调函数,而不是等待当前任务结束后再执行,

这种特性可以保证 Promise回调函数的执行顺序,避免出现竞态条件,从而使代码更加可靠。

补充(举例解释一下):

假设,有两个Promise对象P1、P2,

它们的状态都发生了改变,但是P1的回调函数在微任务队列中先于P2的回调函数执行,

这种情况下,即使P2的状态改变发生在P1之前,P2的回调函数也会等待P1的回调函数执行完毕后再执行,

这样可以避免多个回调函数同时执行而产生的竞态条件。

如果当前任务执行一半了,Promise状态发生改变了,会停下当前任务去执行微任务队列中Promise的回调函数吗??????

不会的,

即使当前任务执行了一半,如果Promise状态发生改变,也会立即执行微任务队列中的回调函数,

但是不会停下当前任务的执行。当前任务的执行会继续完成,然后才会回到微任务队列中执行其他的回调函数,

因此,即使Promise状态发生改变,也不会直接打断当前任务的执行。

2、

另一方面,宏任务的执行是在当前任务结束后才会执行的,这意味着,可以将一些耗时的操作放入宏任务队列中,从而避免阻塞当前任务的执行,

比如,我们可以将一些需要等待一段时间才能执行的代码放入 setTimeout 的回调函数中,

这样可以使页面在执行这些代码的同时仍然保持响应,提高用户体验。

因此,JavaScript 之所以要区分微任务和宏任务,是为了保证异步操作的正确性和性能。


JS中微任务和宏任务执行顺序

1、首先执行当前代码(同步任务),直到遇到第一个宏任务或微任务,

2、如果遇到微任务,则将它添加到微任务队列中,继续执行同步任务,

3、如果遇到宏任务,则将它添加到宏任务队列中,继续执行同步任务,

4、当前任务执行完毕后,JavaScript引擎会先执行所有微任务队列中的任务,直到微任务队列为空,

5、然后执行宏任务队列中的第一个任务,直到宏任务队列为空,

重复步骤4、步骤5,直到所有任务都被执行完毕。

需要注意的是,微任务比宏任务优先级要高,因此在同一个任务中,如果既有微任务又有宏任务,那么微任务会先执行完毕,

而在不同的任务中,宏任务的执行优先级要高于微任务,因此在一个宏任务执行完毕后,它才会执行下一个宏任务和微任务队列中的任。

  • 举个例子,

假设当前代码中有一个 setTimeout(宏任务) 和一个 Promise(微任务),它们分别对应一个宏任务和一个微任务。那么执行顺序如下:

1、执行当前代码,将 setTimeout 和 Promise 添加到宏任务和微任务队列中,

2、当前任务执行完毕,JavaScript引擎,先执行微任务队列中的 Promise回调函数,

3、微任务队列为空后,再执行宏任务队列中的 setTimeout回调函数。

需要注意的是:在一些特殊情况下,微任务和宏任务的执行顺序可能会发生变化,比如在使用 MutationObserver 监听 DOM 变化时,

它会被视为一个微任务,但是它的执行顺序可能会比其他微任务更靠后。因此,需要根据具体情况来理解和处理微任务和宏任务的执行顺序。


看下面几个案例

1、微任务:Promise回调函数,宏任务:setTimeout回调函数

javascript 复制代码
console.log('start')

setTimeout(() => console.log('setTimeout'), 0)

Promise.resolve()
	.then(() => console.log('Promise'))
	
console.log('end')

// start、end、Promise、setTimeout

首先输出 start,然后通过 setTimeout 方法注册了一个回调函数,它会被添加到宏任务队列中,

接着创建了一个 Promise实例,并且通过 then方法注册了一个回调函数,在 Promise对象的状态改变时会执行这个回调函数,

接着输出 end,

因为 Promise回调函数是微任务,所以它会被添加到微任务队列中,等待执行,

等到主线程的同步任务执行完毕后,JavaScript引擎会先执行微任务队列中的任务,输出 Promise,然后执行宏任务队列中的任务,输出 setTimeout。


2、微任务:process.nextTick回调函数,宏任务setImmediate 回调函数

javascript 复制代码
console.log('start')

setImmediate(() => console.log('setImmediate'))

process.nextTick(() => console.log('process.nextTick'))

console.log('end')

// start、end、process.nextTick、setImmediate

在Node.js环境中,process.nextTick回调函数是排在微任务队列最前面的,优先级比 Promise回调函数还要高,

而 setImmediate回调函数是排在宏任务队列最后面的。

所以,以上代码会先输出 start,然后输出 end,

等到主线程的同步任务执行完毕后,JavaScript引擎会先执行微任务队列中的任务,输出 process.nextTick,然后执行宏任务队列中的任务,输出 setImmediate。


3、微任务:Promise回调函数,宏任务:requestAnimationFrame回调函数

javascript 复制代码
console.log('start')

requestAnimationFrame(() => console.log('requestAnimationFrame'))

Promise.resolve()
	.then(() => console.log('Promise'))
	
console.log('end')

// start、end、Promise、requestAnimationFrame

首先输出 start,然后通过 requestAnimationFrame方法注册了一个回调函数,它会被添加到宏任务队列中,

接着创建了一个 Promise实例,并且通过 then方法注册了一个回调函数,在 Promise对象的状态改变时会执行这个回调函数,

接着输出 end,

因为 Promise回调函数是微任务,所以它会被添加到微任务队列中,等待执行,

等到主线程的同步任务执行完毕后,JavaScript引擎会先执行微任务队列中的任务,输出 Promise,然后执行宏任务队列中的任务,输出 requestAnimationFrame。


4、微任务:Promise回调函数,宏任务:XMLHttpRequest

javascript 复制代码
console.log('start')

// 创建 XMLHttpRequest对象并设置 onload回调函数。这个函数会在 HTTP请求成功完成后被调用。
const xhr = new XMLHttpRequest()
xhr.onload = () => console.log('XMLHttpRequest')
xhr.open('GET', 'someurl')
xhr.send()

Promise.resolve()
  .then(() => console.log('Promise'))

console.log('end')

// start、end、Promise、XMLHttpRequest

首先输出 start,然后创建了一个 XMLHttpRequest对象,并且通过 open方法 和 send方法发送了一个 GET请求,

接着通过 onload方法注册了一个回调函数,在请求成功后会执行这个回调函数,

接着创建了一个 Promise实例,并且通过 then方法注册了一个回调函数,在 Promise对象的状态改变时会执行这个回调函数,

接着输出 end,

因为 Promise回调函数是微任务,所以它会被添加到微任务队列中,等待执行,

等到主线程的同步任务执行完毕后,JavaScript引擎会先执行微任务队列中的任务,输出 Promise,

然后等待 XMLHttpRequest对象的回调函数执行,

当请求成功后,JavaScript引擎会执行宏任务队列中的任务,输出 XMLHttpRequest。

  • 请注意,这个执行顺序取决于网络请求的速度和主线程的同步任务执行速度。如果网络请求速度较慢,或者主线程的同步任务执行速度较慢,那么 Promise 和 XMLHttpRequest 的执行顺序可能会稍有不同。
相关推荐
满怀101521 分钟前
【Vue 3全栈实战】从响应式原理到企业级架构设计
前端·javascript·vue.js·vue
伟笑1 小时前
elementUI 循环出来的表单,怎么做表单校验?
前端·javascript·elementui
确实菜,真的爱1 小时前
electron进程通信
前端·javascript·electron
魔术师ID3 小时前
vue 指令
前端·javascript·vue.js
Clown953 小时前
Go语言爬虫系列教程 实战项目JS逆向实现CSDN文章导出教程
javascript·爬虫·golang
星空寻流年4 小时前
css3基于伸缩盒模型生成一个小案例
javascript·css·css3
waterHBO5 小时前
直接从图片生成 html
前端·javascript·html
EndingCoder5 小时前
JavaScript 时间转换:从 HH:mm:ss 到十进制小时及反向转换
javascript
互联网搬砖老肖6 小时前
React组件(一):生命周期
前端·javascript·react.js
HCl+NaOH=NaCl+H_2O6 小时前
Quasar组件 Carousel走马灯
javascript·vue.js·ecmascript