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 的执行顺序可能会稍有不同。
相关推荐
I_Am_Me_12 分钟前
【JavaEE进阶】 JavaScript
开发语言·javascript·ecmascript
℘团子এ22 分钟前
vue3中如何上传文件到腾讯云的桶(cosbrowser)
前端·javascript·腾讯云
学习前端的小z28 分钟前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
前端百草阁1 小时前
【TS简单上手,快速入门教程】————适合零基础
javascript·typescript
彭世瑜1 小时前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
Backstroke fish1 小时前
Token刷新机制
前端·javascript·vue.js·typescript·vue
zwjapple1 小时前
typescript里面正则的使用
开发语言·javascript·正则表达式
小五Five1 小时前
TypeScript项目中Axios的封装
开发语言·前端·javascript
临枫5411 小时前
Nuxt3封装网络请求 useFetch & $fetch
前端·javascript·vue.js·typescript
酷酷的威朗普1 小时前
医院绩效考核系统
javascript·css·vue.js·typescript·node.js·echarts·html5