Skip to main content

宏任务与微任务

宏任务和微任务到底是什么

两者其实都是异步, 可以简单的说是与 定时器 相关的都是宏任务。 复杂的说可以说是 进程切换的都是宏任务,因为消耗了大量的资源,线程切换相关的都是微任务

详细的说线程的切换,是因为纤程的切换导致的, 类似 js 里面的 async await,因为 async await 本身是一个迭代器,迭代器利用了线程

宏任务有: scrip(JS 整体代码)、setTimeout、setInterval、setImmediate、I/O、UI 交互、Ajax、DOM 还有其他设计成不被 js 阻塞的,会单独开启一个进程进行管理的都是宏任务

微任务有: Promise(重点关注)、process.nextTick(Node.js)、MutaionObserver(也就是观察的 DOM 的,不是 DOM 操作本身)

info
  1. 为什么定时器相关的都是宏任务呢?

因为计时任务是实时的,不可以被阻塞,所以定时器要放到另一个进程被管理,一般是 setTimeout,setInterval、setImmediate

  1. 事件之类的为什么是微任务呢?

简单的说,事件触发依赖于浏览器实现,浏览器有自己的事件注册和分发机制,不会与 js 存在于同一个进程中, 事件会有属于自己的单独进程,也就是 MutationObserver

MutationObserver 是观察 DOM,对 DOM 的变化作出自己的响应,所以会在专门的 DOM 管理进程中,不会进行进程的切换

渲染之类的也一样,属于自己本身的进程中,等待自己进程的队列执行

tip

微任务在执行的时候,可以获取到任务之外的上下文。而宏任务在执行的时候,获取不到任务之外的上下文

执行顺序

先执行微任务再执行宏任务

可以这么理解,微任务因为是在线程里面,所以执行快一点。宏任务切换是要在进程之间切换,需要花费时间,所以慢一点。

微任务不需要执行上下文, 可以在上下文切换的间隔,就把微任务处理掉

代码演示

题目 1

// 宏任务
setTimeout(function () {
console.log('1')
})

new Promise(function (resolve) {
// 主线程任务
console.log('2')
resolve()
}).then(function () {
// 微任务
console.log('3')
})

// 主线程任务
console.log('4')

output: 2 4 3 1

setTimeout 是宏任务,会等微任务都执行完成才去执行

题目 2

setTimeout(() => {
//宏任务队列1
console.log('1') //宏任务队1的任务1

setTimeout(() => {
//宏任务队列3(宏任务队列1中的宏任务)
console.log('2')
}, 0)

new Promise((resolve) => {
resolve()
console.log('3') //宏任务队列1的任务2
}).then(() => {
//宏任务队列1中的微任务
console.log('4')
})
}, 0)

setTimeout(() => {
//宏任务队列2
console.log('5')
}, 0)

console.log('6') //同步主线程

output: 6 1 3 4 5 2

第一轮的输出肯定是同步任务的 6,遇到两个 setTimeout 宏任务,放到队列执行

第二轮对两个宏任务进行顺序执行,先输出 1 ,遇到了下一个 setTimeout, 放到下一个宏任务队列中。然后遇到构造函数的同步任务,输入 3,then 方法为微任务,放到微任务的队列中。然后执行到下个宏任务 5,放到宏任务队列。此时只有一个微任务和宏任务的队列(包含下一个宏任务 2),输出微任务 4

然后只剩一个宏任务(里面包含另一个宏任务),也就是输出外层的 5 ,最后输出 2

题目 3

console.log('1')

setTimeout(function () {
console.log('2')

setTimeout(() => {
console.log('3')
})

new Promise(function (resolve) {
console.log('4')
resolve()
}).then(function () {
console.log('5')
})
})

setTimeout(() => {
console.log(6)
})

new Promise(function (resolve) {
console.log('7')
resolve()
}).then(function () {
console.log('8')
})

setTimeout(function () {
console.log('9')

setTimeout(() => {
console.log('10')
})

new Promise(function (resolve) {
console.log('11')
resolve()
}).then(function () {
console.log('12')
})
})

output: 1 7 8 2 4 5 6 9 11 12 3 10

原理与上述相同

题目 4

async await

async 和 await 其实就是 Generator 和 Promise 的语法糖

async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}

用 promise 翻译过来就是

async function async1() {
console.log('async1 start')
Promise.resolve(async2()).then(() => console.log('async1 end'))
}

例子如下:

async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}

async function async2() {
console.log('async2')
}

async1()

setTimeout(() => {
console.log('timeout')
}, 0)

new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})

console.log('script end')

output:

async1 start

async2

promise1

script end

async1 end

promise2

timeout

因为上述代码翻译过来就是

async function async1() {
console.log('async1 start')

Promise.resolve(() => {
console.log('async2')
}).then(() => {
console.log('async1 end')
})
}

async1()

setTimeout(() => {
console.log('timeout')
}, 0)

new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})

console.log('script end')

函数在调用的时候才会被执行

输出顺序也就是先输出同步任务的 async1 startasync2promise1script end

然后输出两个 then 微任务 async1 endpromise2

最后才是宏任务的 timeout

题目 5

如果把上述代码改成 node 环境的 nextTick

console.log('1')

setTimeout(function () {
console.log('2')

process.nextTick(function () {
console.log('3')
})

new Promise(function (resolve) {
console.log('4')
resolve()
}).then(function () {
console.log('5')
})
})

process.nextTick(function () {
console.log('6')
})

new Promise(function (resolve) {
console.log('7')
resolve()
}).then(function () {
console.log('8')
})

setTimeout(function () {
console.log('9')

process.nextTick(function () {
console.log('10')
})

new Promise(function (resolve) {
console.log('11')
resolve()
}).then(function () {
console.log('12')
})
})

output: 1 7 6 8 2 4 3 5 9 11 10 12

process.nextTick 是一个微任务

题目 6

如果是两个宏任务,就依靠于自己的时间延时去执行

setTimeout(() => {
console.log('1')
}, 1000)

setTimeout(() => {
console.log('2')
}, 1000)

setInterval(() => {
console.log('setInterval')
}, 1000)