明白概念
JavaScript是单线程的语言,从上到下执行,先执行同步任务,再执行异步任务,异步任务分为宏任务(macro)和微任务(micro)
了解异步执行的运行机制
Js 的事件循环(Event Loop)机制以及实例讲解大家都知道js是单线程的脚本语言,在同一时间,只能做同一件事,为 - 掘金 (juejin.cn)
- 所有任务都在主线程上执行,形成一个执行栈。
- 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
- 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列"。那些对应的异步任务,结束等待状态,进入执行栈并开始执行。
- 主线程不断重复上面的第三步。
看看常见的任务
常见的宏任务包括:
setTimeoutsetIntervalsetImmediate(Node.js 独有)requestAnimationFrame(浏览器独有)- I/O 操作
- UI 渲染 (浏览器独有)
常见的微任务包括:
Promise.then、Promise.catch、Promise.finallyprocess.nextTick(Node.js 独有)MutationObserver(浏览器独有)queueMicrotaskAPI
记住事件循环执行顺序
在 JavaScript 中,事件循环和任务队列的执行顺序至关重要,尤其是在处理宏任务和微任务时。这里是一步步的解释,展示了宏任务和微任务是如何在事件循环中被调度和执行的:
-
开始执行脚本:浏览器或 Node.js 开始执行脚本文件,这是第一个宏任务。
-
宏任务执行:在当前宏任务执行过程中,可能会注册更多的宏任务和微任务。当前宏任务执行完成之后,不会立即执行下一个宏任务。
-
执行所有微任务:在当前宏任务完成后,事件循环会检查微任务队列。如果存在微任务,则执行所有微任务。这包括由
Promise.then、Promise.catch、Promise.finally生成的回调,以及queueMicrotask()添加的任务。微任务队列必须清空才能继续下一个宏任务。- 微任务中注册的微任务:如果在执行微任务期间又添加了新的微任务,这些新的微任务也会在当前所有微任务结束之前执行完毕。
-
渲染更新(仅在浏览器环境中):如果有必要,浏览器将处理渲染更新,如重绘和回流。这通常发生在执行完所有微任务之后,再开始下一轮宏任务之前。
-
下一个宏任务:完成微任务和可能的渲染更新后,事件循环将从宏任务队列中取出下一个任务开始执行,重复上述步骤。
-
循环重复:这个过程不断重复,直到宏任务队列为空。
理解一个具体的例子
假设我们有以下 JavaScript 代码:
console.log('1. Script start');
setTimeout(() => {
console.log('2. setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('3. Promise 1');
}).then(() => {
console.log('4. Promise 2');
});
console.log('5. Script end');
执行顺序如下:
- 1. Script start:这是首个宏任务的一部分。
- 5. Script end:仍然是首个宏任务的一部分。
- 3. Promise 1:第一个宏任务结束后,执行所有微任务。
Promise.resolve()创建的微任务现在被执行。 - 4. Promise 2:前一个微任务中添加的另一个微任务。
- 2. setTimeout:第二轮宏任务开始,执行
setTimeout。
这个执行顺序展示了微任务总是在当前宏任务完成后立即执行,且必须在下一个宏任务开始之前清空微任务队列。