JavaScript - Microtasks (微任务)
JavaScript 中的微任务是一些小函数,它们在创建它们的函数或程序代码完成后,且 JavaScript 执行堆栈为空时执行。微任务在任何宏任务(例如 setImmediate() 和 setTimeout())之前执行。微任务用于实现诸如 Promise 之类的功能。
JavaScript 是一种单线程编程语言。但是,您可以使用 Promise、回调和异步函数并行运行 JavaScript 代码。
JavaScript 基于事件循环运行代码。事件循环负责执行代码、处理代码、收集事件数据以及执行子任务。
让我们先了解一下 JavaScript 事件循环。
JavaScript 事件循环
事件循环逐行执行 JavaScript 代码。它将代码添加到调用堆栈(一个执行该代码的队列)中。
JavaScript 包含两种类型的队列来执行任务。
- 微任务队列
- 宏任务队列
当调用堆栈队列为空时,事件循环将执行微任务队列中的所有任务。之后,它会执行宏任务队列中的所有函数和代码。
在理解了微任务和宏任务之后,我们将更深入地了解 JavaScript 代码的执行过程。
JavaScript 中的微任务是什么?
在 JavaScript 中,微任务是由 Promise 或异步函数生成并在稍后执行的短函数。
以下是微任务列表。
- Promise 回调
- 队列微任务
在使用 Promise 代码时,无论你传递了哪个回调函数作为 then()、catch() 或 finally() 方法的参数,它都会被添加到微任务队列中。
首先,JavaScript 运行引擎执行整个脚本,将主线程中的代码添加到调用堆栈,然后微任务放入微任务队列。当调用堆栈的所有任务执行完成后,微任务队列中所有任务的执行也随之完成。
让我们通过下面的示例来理解。
示例
在下面的代码中,我们在脚本开始时打印开始消息,在脚本结束时打印结束消息。
在中间,我们定义了一个 Promise,它会立即被解决。之后,我们使用 then() 方法消费了 Promise,并打印了 Promise 返回的消息。
<html>
<body>
<div id = "output"> </div>
<script>
const output = document.getElementById("output");
output.innerHTML += "The start of the code execution. <br>";
// Creating the promise
let promise = new Promise(function (resolve, reject) {
resolve("The promise is resolved. <br>");
});
// Consuming the promise code
promise.then(function (result) {
output.innerHTML += result;
});
output.innerHTML += "The end of the code execution. <br>";
</script>
</body>
</html>
输出
The start of the code execution. The end of the code execution. The promise is resolved.
上面代码的输出中发生了一件有趣的事情。
在输出中,您可以看到它最终打印了开始、结束和承诺消息。
现在,问题是为什么会发生这种情况。答案是,then() 方法的回调函数会被添加到微任务队列中,并且只有当调用堆栈为空时才会执行。
什么是宏任务?
现在,让我们了解一下什么是宏任务。
宏任务也是一个简短的函数,它会在所有代码执行完毕后执行,这些代码位于调用堆栈和微任务队列中。
JavaScript 运行时引擎会将宏任务添加到微任务队列中。
以下方法生成的回调函数会被添加到宏任务队列中。
- setTimeout
- setInterval
- setImmediate
让我们通过下面的示例来理解宏任务。
示例
在下面的代码中,我们添加了开始消息, setTimeOut() 方法,并结束消息。
在 setTimeOut() 方法中,我们将回调函数作为第一个参数传递,在输出中打印消息,并设置 0 秒延迟。
<html>
<body>
<div id = "demo"> </div>
<script>
let output = document.getElementById("demo");
output.innerHTML += "The start of the code execution.<br>";
setTimeout(function () {
output.innerHTML += "The code execution is being delayed for 0 seconds. <br>";
}, 0);
output.innerHTML += "The end of the code execution.<br>";
</script>
</body>
</html>
输出
The start of the code execution. The end of the code execution. The code execution is being delayed for 0 seconds.
上面代码的输出也很有趣。
它首先打印开始消息,然后打印结束消息,最后打印 setTimeOut() 方法的消息。
这里,我们为 setTimeOut() 方法设置了 0 延迟。尽管如此,它仍然在最后执行,因为 JavaScript 运行引擎将回调函数添加到了宏任务队列中。
让我们通过下面的示例来理解微任务和宏任务。
示例
在下面的代码中,我们添加了延迟为 0 的 setTimeOut() 方法,回调函数打印了消息。
之后,我们使用 Promise() 构造函数定义了一个 Promise,并使用 then() 方法使用了 Promise 代码。
最后,我们打印了 end 方法。
<html>
<body>
<div id = "output"> </div>
<script>
let output = document.getElementById("output");
output.innerHTML += "Start <br>";
setTimeout(function () { // Macro task
output.innerHTML += "In setTimeOut() method. <br>";
}, 0);
let promise = new Promise(function (resolve, reject) {
resolve("In Promise constructor. <br>");
});
promise.then(function (value) { // Micro tasks
output.innerHTML += value;
});
output.innerHTML += "End <br>";
</script>
</body>
</html>
输出
Start End In Promise constructor. In setTimeOut() method.
让我们理解一下上面示例的输出。
首先,由于 JavaScript 调用堆栈的存在,它打印了 start 消息。
之后,它将 setTimeOut() 方法的回调函数添加到宏任务队列中。
接下来,它将 then() 方法的回调函数添加到微任务队列中。
接下来,它执行代码的最后一行并打印 End 消息。
现在,调用堆栈为空。因此,它执行了微任务队列中的所有任务。因此,它完成了 then() 方法回调函数的执行。
现在,调用堆栈和微任务队列都为空。因此,它执行了宏任务队列中的所有任务,并完成了 setTimeOut() 方法回调函数的执行。
本章演示了 JavaScript 运行引擎如何执行代码。如果要改变代码的执行顺序,可以谨慎使用微任务和宏任务。

