# 说在前面

JavaScript 为单线程,意为需要根据任务队列一个任务一个任务的执行下去,任务分为同步任务与异步任务,这两个任务放在不同的地方执行。同步任务放在 主线程 中排队执行,而异步任务则放在 任务队列 中,在需要执行的时刻才会加入主线程中排队执行。异步任务分为 宏任务微任务 ,而微任务的执行要先于宏任务,同步方法执行先于异步任务。

宏任务:(macro) task,可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。
浏览器为了能够使得 JS 内部 (macro) task 与 DOM 任务能够有序的执行,会在一个 (macro) task 执行结束后,在下一个 (macro) task 执行开始前,对页面进行重新渲染,流程如下:

(macro) task-> 渲染 ->(macro) task->...

微任务:microtask, 可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前 task 后,下一个 task 之前,在渲染之前。
所以它的响应速度相比 setTimeout(setTimeout 是 task)会更快,因为无需等渲染。也就是说,在某一个 macrotask 执行完后,就会将在它执行期间产生的所有 microtask 都执行完毕(在渲染前)。

# 参考资源

https://zhuanlan.zhihu.com/p/78113300

# 宏任务与微任务分类

# 宏任务

  • script (整体代码)
  • setTimeout
  • setInterval
  • I/O
  • UI 交互事件
  • postMessage
  • MessageChannel
  • setImmediate (Node.js 环境)

# 微任务

  • Promise.then
  • Object.observe
  • MutationObserver
  • process.nextTick (Node.js 环境)

# 异步任务调度流程

  • 执行一个宏任务
  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务执行完毕后,将微任务队列中的所有微任务依次执行
  • 当前宏任务执行完毕,开始下一个宏任务

# 代码示例 A

1
2
3
4
5
6
7
8
9
10
11
12
13
// 宏任务
setTimeout(() => console.log(1), 0)

new Promise(resolve => {
// 主线程
resolve()
console.log(2)
}).then(_ => {
// 微任务
console.log(3)
})
// 主线程
console.log(4)

上述代码的执行结果为: 2431

# 流程描述

  1. settimeout 为异步方法,是宏任务,放在任务队列中
  2. Promise 的构造函数,为同步方法,放在主线程中
  3. Promise.then 为异步方法,是微任务,放在微任务队列中
  4. console.log 为同步方法,放在主线程中
  5. 首先先处理所有同步函数,输出 2 4
  6. 开始处理异步函数,微任务处理优先于宏任务,所以依次执行微任务得到结果 3
  7. 处理宏任务,得到结果 1

# 代码示例 B

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
console.log(1)
setTimeout(() => {
console.log(2)
new Promise(resolve => {
console.log(3)
resolve()
}).then(()=>{
console.log(4)
})
})
new Promise(resolve=>{
console.log(5)
resolve()
}).then(()=>{
console.log(6)
})
setTimeout(()=>{
console.log(7)
new Promise(resolve=>{
console.log(8)
resolve()
}).then(()=>{
console.log(9)
})
})

上述代码的执行结果为: 156234789

# 流程描述

  1. console.log(1) 为同步方法,放在主线程中
  2. setTimeout 为异步方法,宏任务,放在任务队列中
  3. Promise 构造函数为同步方法,放在主线程中
  4. Promise.then 为异步方法,微任务,放在微任务队列中
  5. setTimeout 为异步方法,宏任务,放在任务队列中
  6. 依次执行主线程中的任务,首先是同步方法,得到结果 1 5
  7. 执行微任务队列中的任务,得到结果 6
  8. 执行任务队列中的任务,第一个为 setTimeoutconsole.log(2) 为同步方法,放在主线程中
  9. Promise 构造函数为同步方法,放在主线程中
  10. Promise.then 为异步方法,微任务,放在微任务队列中
  11. 依次执行主线程中的任务,同步方法,得到结果 2 3
  12. 执行微任务队列中的任务,得到结果 4
  13. 执行第二个任务队列中的 setTimeout ,流程同上,得到结果 7 8 9

# 代码示例 C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
setTimeout(function () {
console.log("setTimeout1");
new Promise(function (resolve) {
resolve();
}).then(function () {
new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then4");
});
console.log("then2");
});
});

new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("then1");
});

setTimeout(function () {
console.log("setTimeout2");
});

console.log(2);

queueMicrotask(() => {
console.log("queueMicrotask1")
});

new Promise(function (resolve) {
resolve();
}).then(function () {
console.log("then3");
});

# 结果

promise1 2 then1 queueMicrotask1 then3 setTimeout1 then2 then4 setTimeout2

更新于 阅读次数