宏任务与微任务
原文:做一些动图,学习一下EventLoop (https://juejin.cn/post/6969028296893792286)
一、任务队列
JavaScript 是单线程执行的语言, 在同一时间只能干一件事情。如果前面的任务很耗时后面的任务就会一直等待,为了解决这个问题,js中出现了同步任务和异步任务
1.1 同步任务
在主线程上排队执行的任务只有前面执行完毕才能执行下一个任务,形成一个执行栈
1.2 异步任务
不进入主线程,而是进入任务队列,只有当主线程的任务执行完毕,才会从任务队列中拿任务到主线程来执行。由于主线程不断循环 获取任务 -> 执行任务 -> 再次获取任务 -> 再次执行任务
,所以这种机制被叫做 事件循环
。
二、宏任务和微任务
在任务队列中其实还分为宏任务队列和微任务队列,里面存放的就是宏任务和微任务
宏任务和微任务都是异步任务, 这两者的区别就是它们的执行顺序。
在同步任务中, 任务都是讲究按照代码顺序执行的, 而异步任务也是需要按照顺序执行的; 队列的属性就说先进先出, 因此异步任务会按照进入队列的顺序以此执行。
这就解释了为什么下列代码中,为什么后面的定时器比前面的定时器先执行。因为后者的定时器先推进宏任务队列,而前者会在到时间后再被推进宏任务队列
setTimeout(() => {
console.log('a');
}, 10000);
setTimeout(() => {
console.log('b');
}, 100);
2.1 宏任务
宏任务包括:script(整体代码)
, setTimeout
, setInterval
, Ajax
, DOM事件
, requestAnimationFrame ( 只存在浏览器中 )
, I/O
, SetImmediate( node )
2.2 微任务
微任务包括:Promise.then
, async/await
, Object.observe ( 已废弃 )
, MutationObserver ( HTML5新特性, 只存在浏览器中 )
, process.nextTick ( node )
三、完整的执行顺序
- 从上往下执行所有的同步代码
- 在执行过程中遇到宏任务就存放到宏任务队列中, 遇到微任务就存放到微任务队列中
- 当所有的同步任务执行完毕, 就执行微任务队列中满足需求的所有回调( process.nextTick 优先 )
- 当微任务队列中所有满足需求的回调执行完毕后, 就执行宏任务队列中满足需求的所有回调 ( SetImmediate 最后 )
3.1 注意
- 每执行完一个宏任务都会立即检查微任务列表有没有清空, 如果没有就立即清空。
- Promise里的是同步任务,立即执行
- Promise.then() 中如果不写函数的话也是同步任务
四、练习题
4.1 第一题
console.log(100)
setTimeout(() => {
console.log(200)
})
Promise.resolve().then(() => {
console.log(300)
})
console.log(400)
// 100、400、300、200
// 1. 第一遍执行同步任务输出 100、400
// 2. 遇到 setTimeout 将其加入宏任务队列、Promise.then 将其加入微任务队列
// 3. 先执行微任务输出 300
// 4. 再执行宏任务输出 200
4.2 第二题
setTimeout(function() {
console.log('a')
});
new Promise(function(resolve) {
console.log('b');
for (var i = 0; i < 10000; i++) {
i == 99 && resolve();
}
}).then(function() {
console.log('c')
});
console.log('d');
// b、d、c、a
// 1. 先执行同步任务, 输出b、d ( 注意:Promise里的是同步任务 )
// 2. 遇到 setTimeout 将其加入宏任务队列、Promise.then 将其加入微任务队列
// 3. 先执行微任务输出 c
// 4. 再执行宏任务输出 a
4.3 第三题
let promise = new Promise((resolve) => {
setTimeout(() => {
console.log('a')
resolve()
}, 0)
console.log('b')
}).then(value => console.log('c'))
console.log('d')
// b、d、a、c
// 解析:
// 读取到第 2 行, 将 setTimeout 加入宏任务队列
// 读取到第 6 行,执行输出 b
// 读取到第 7 行,因为还没有执行 resolve() 所以暂时不会创建 Promise.then
// 读取到第 9 行,执行输出 d
// 由于没有微任务,开始执行宏任务
// 读取 setTimeout 的代码, 输出 a, 并且创建 Promise.then
// 执行微任务,输出 c
4.4 第四题
console.log('a');
setTimeout(function() {
console.log('b');
process.nextTick(function() {
console.log('c');
})
new Promise(function(resolve) {
console.log('d');
resolve();
}).then(function() {
console.log('e')
})
})
process.nextTick(function() {
console.log('f');
})
new Promise(function(resolve) {
console.log('g');
resolve();
}).then(function() {
console.log('h')
})
setTimeout(function() {
console.log('i');
process.nextTick(function() {
console.log('j');
})
new Promise(function(resolve) {
console.log('k');
resolve();
}).then(function() {
console.log('l')
})
})
// 第一轮
// 1.1. 进入主线程,遇到 console.log('a'), 执行打印 a
// 1.2. 遇到 setTimeout 将其加入到 宏任务队列
// 1.3. 遇到 process.nextTick 将其加入到微任务队列
// 1.4. 遇到 Promise 将同步任务 g 打印
// 1.5. 将 Promise.then 加入到微任务队列
// 1.6. 遇到第二个 setTimeout 将其加入到 宏任务队列
// 打印情况: a、g
// 宏任务( 由底到高 ):setTimeout (P3)、setTimeout (P26)
// 微任务( 由底到高 ):process.nextTick (P16)、Promise.then (P22)
// 1.7 查找微任务队列,由下开始执行
// 1.8 执行 process.nextTick, 打印 f
// 1.9 执行 Promise.then, 打印 h
// 打印情况: a、g、f、h
// 第二轮
// 2.1 从宏任务队列底部开始执行 setTimeout (P3)
// 2.2 遇到 console.log('b') 打印 b
// 2.3 遇到 process.nextTick 加入到微任务队列
// 2.4 遇到 Promise 将同步任务 d 打印
// 2.5 将 Promise.then 加入到微任务队列
// 打印情况: a、g、f、h、b、d
// 宏任务( 由底到高 ):setTimeout (P26)
// 微任务( 由底到高 ):process.nextTick (P5)、Promise.then (P11)
// 2.6 查找微任务队列,由下开始执行
// 2.7 执行 process.nextTick, 打印 c
// 2.8 执行 Promise.then, 打印 e
// 打印情况: a、g、f、h、b、d、c、e
// 第三轮
// 3.1 从宏任务队列底部开始执行 setTimeout (P26)
// 3.2 遇到 console.log('i') 打印 i
// 3.3 遇到 process.nextTick 加入到微任务队列
// 3.4 遇到 Promise 将同步任务 k 打印
// 3.5 将 Promise.then 加入到微任务队列
// 打印情况: a、g、f、h、b、d、c、e、i、k
// 宏任务( 由底到高 ):空
// 微任务( 由底到高 ):process.nextTick (P28)、Promise.then (P35)
// 3.6 查找微任务队列,由下开始执行
// 3.7 执行 process.nextTick, 打印 j
// 3.8 执行 Promise.then, 打印 l
// 打印情况: a、g、f、h、b、d、c、e、i、k、j、l