JavaScript 的任务调度机制是基于 事件循环(Event Loop) 的,它管理了不同类型的任务队列。总的来说,任务调度机制涉及到三种主要的任务队列:
- 同步任务(Synchronous Tasks)
- 微任务(Microtasks)
- 宏任务(Macrotasks)
在某些情况下,我们还可以提到 交互队列 和 延迟队列,它们可以视为宏任务的一部分。以下是它们的详细介绍、执行顺序和示例:
1. 同步任务(Synchronous Tasks)
- 同步任务是指代码中按顺序执行的任务。这些任务会先被执行,直到执行栈清空。
- 特点:同步任务是JavaScript执行的主要任务,必须按顺序执行,无法中断。
执行顺序:
- 同步任务先执行。
示例:
console.log('Start');
console.log('End');输出顺序:
Start
End2. 微任务(Microtasks)
微任务是指那些在同步任务执行完后、下一个宏任务开始前执行的任务。微任务的优先级比宏任务高,因此它们会在当前栈清空后立刻执行。
常见的微任务:
Promise的.then()、.catch()、.finally()回调queueMicrotask()MutationObserver
执行顺序:
- 执行同步任务。
- 执行所有微任务,直到微任务队列清空。
- 然后才会开始执行宏任务。
示例:
console.log('Start');
setTimeout(() => {
console.log('Timeout 1');
}, 0);
Promise.resolve().then(() => {
console.log('Promise 1');
});
setTimeout(() => {
console.log('Timeout 2');
}, 0);
Promise.resolve().then(() => {
console.log('Promise 2');
});
console.log('End');输出顺序:
Start
End
Promise 1
Promise 2
Timeout 1
Timeout 2执行过程:
- 先执行同步代码:
Start和End。 - 然后执行微任务队列中的任务:
Promise 1和Promise 2。 - 最后执行宏任务队列中的任务:
Timeout 1和Timeout 2。
- 先执行同步代码:
3. 宏任务(Macrotasks)
宏任务包括所有的异步任务和UI更新任务。它们的执行顺序是按队列顺序来执行的,每次执行一个宏任务。
常见的宏任务:
setTimeout、setIntervalI/O 操作(例如文件读取、网络请求等)- 用户交互事件(点击、键盘输入等)
执行顺序:
- 执行同步任务。
- 执行所有微任务。
- 执行宏任务(例如
setTimeout、事件监听等)。
示例:
setTimeout(() => {
console.log("Timeout 1");
}, 0);
setTimeout(() => {
console.log("Timeout 2");
}, 0);
console.log("Start");输出顺序:
Start
Timeout 1
Timeout 2执行过程:
- 先执行同步代码:
Start。 - 然后执行宏任务队列中的任务:
Timeout 1和Timeout 2。
- 先执行同步代码:
4. 交互队列(Interaction Queue)
交互队列包含与用户交互相关的事件,如点击、输入框事件等。这些事件会作为宏任务的一部分,通常是在宏任务执行后、微任务执行前执行。
常见的交互队列事件:
click、keydown、input等。
执行顺序:
- 宏任务中的异步任务完成后,事件循环会处理交互队列中的任务。
示例:
document.body.addEventListener('click', () => {
console.log('Click event');
});执行过程:
- 用户点击页面后,点击事件会被加入到交互队列,并在下一轮事件循环中执行。
5. 延迟队列(Delayed Queue)
延迟队列实际上是宏任务队列的一部分,专门处理带有延迟时间的任务(如 setTimeout 和 setInterval)。这些任务会在延迟时间到期后进入队列,并作为宏任务执行。
常见的延迟队列任务:
setTimeout、setInterval
执行顺序:
- 延迟队列的任务是宏任务的一部分,会在当前宏任务执行完后、下一个宏任务之前执行。
示例:
setTimeout(() => {
console.log('Delayed Timeout');
}, 1000);执行过程:
- 任务会在大约 1000 毫秒后被加入宏任务队列。
总结:JavaScript 任务调度机制的队列
JavaScript 主要通过事件循环管理不同的任务队列,每种队列的任务执行顺序如下:
- 同步任务:首先执行,执行栈中的代码。
- 微任务队列:在同步任务执行后、宏任务之前执行。优先级较高。
- 宏任务队列(包含延迟队列):在微任务执行完后执行。宏任务包含
setTimeout、setInterval等异步任务。 - 交互队列:处理与用户交互相关的事件,通常在宏任务和微任务之间执行。
执行流程:
- 执行同步任务。
- 执行所有微任务,直到微任务队列为空。
- 执行交互队列中的任务。
- 执行宏任务队列中的任务(如延迟队列中的
setTimeout回调)。
示例总结:
console.log("Start");
setTimeout(() => {
console.log("Timeout 1");
}, 0);
Promise.resolve().then(() => {
console.log("Promise 1");
});
document.body.addEventListener('click', () => {
console.log("Click Event");
});
setTimeout(() => {
console.log("Timeout 2");
}, 0);
console.log("End");输出顺序:
Start
End
Promise 1
Timeout 1
Click Event
Timeout 2事件循环在处理任务时,会先执行同步代码,然后清空微任务队列,接着处理交互队列中的任务,再执行宏任务队列中的任务。