介绍
让我们从一个有趣的事儿开始吧!JavaScript它是单线程上执行所有的操作,但有没有觉得它通过复杂的数据结构,给人一种多线程的感觉?JavaScript的运行模型是基于事件循环,该循环处理代码执行、收集和处理事件以及执行排队的子任务。
在本文中,我们将了解到如何通过将任务分为微任务和宏任务来执行任务。阅读本文章后你将掌握JavaScript的事件循环,微任务和宏任务概念。
事件循环
JavaScript是一种在单线程中工作的同步编程语言。这就意味着它的代码是在主线程中一行一行的执行。
事件循环只是一个监视器,确保调用堆栈和回调队列处于持续通讯的状态。它首先确定调用堆栈是否可用,然后通知回调队列,将回调函数传递给调用堆栈,由调用堆栈来执行它。执行完所有的回调函数后,调用堆栈将退出,并且全局执行上下文将释放。
微任务
在上一节中,我们了解了 JavaScript 中的事件循环。现在,让我们了解微任务。
微任务是一个微小的函数,仅在生成它的函数或程序退出后运行,并且仅在 JavaScript 执行堆栈为空时运行,但在用户代理将控制权返回给控制脚本执行环境的事件循环之前。
此事件循环可以是浏览器的主事件循环,也可以是控制 Web worker 的事件循环。这允许指定的函数运行而不会干扰另一个脚本的执行,同时还确保微任务在用户代理有机会对微任务的操作做出反应之前运行。
示例:process.nextTick、Promises、queueMicrotask、MutationObserver
在发生任何其他事件处理、渲染或“宏任务”之前,将执行所有微任务。这很重要,因为它确保微任务之间的应用程序环境基本保持不变(鼠标坐标没有变化,没有新的网络数据等)。
我们可以使用“queueMicrotask”来计划一个函数异步运行(在当前代码之后),但在渲染更新或处理新事件之前。
实现
下面是一个带有“计数进度条”的示例,该示例与上一个示例类似,但不是“setTimeout”,而是使用 queueMicrotask。如您所见,它会在最后渲染。与同步代码类似:
<!-- JavaScript queueMicrotask() function -->
<div id="progress"></div>
<script>
function function1() {
var progress = document.getElementById("progress");
var i = 0;
var interval = setInterval(function() {
progress.innerHTML = i;
i++;
if (i > 100) {
clearInterval(interval);
}
}, 100);
}
window.queueMicrotask(function1());
</script>
输出
100
在计数达到 100 后执行
宏任务
宏任务是在 JavaScript 执行堆栈和微任务都被执行后运行,它是一个函数。
宏任务表示一个单独的、独立的任务。JavaScript 代码始终在宏任务队列中执行,而微任务队列始终为空。宏任务队列经常与任务队列或事件队列混淆。另一方面,宏任务队列的工作方式与任务队列相同。任务队列用于同步语句,而宏任务队列用于异步语句。
在单个宏任务执行周期中,所有记录的微任务都会一举处理。另一方面,宏任务队列的优先级较低。解析 HTML、生成 DOM、执行主线程 JavaScript 代码以及其他事件(如页面加载、输入、网络事件、计时器事件等)都是宏任务。
示例:setTimeout、setImmediate、requestAnimationFrame、setInterval、requestAnimationFrame 等
实现
我们将实现 setTimeout() 宏任务函数来查看宏任务是如何工作的。
<script>
(function() {
// Start of Async Call
console.log('START');
setTimeout(function cb() {
console.log('Callback 1: A message from Callback.');
});
console.log('A message from main thread.');
setTimeout(function cb1() {
console.log('Callback 2: A message from Callback');
}, 0);
console.log('END');
})();
</script>
如果延迟为零,则回调不会在零毫秒后启动。当以 0(零)毫秒的延迟调用 setTimeout 时,回调函数不会在指定的时间间隔后执行。在上面的例子中,我们演示了 setTimeout() 函数。
在上面的示例中,输出“Hello”将在延迟 3000 毫秒后显示。
常见问题
在JavaScript中,微任务与宏任务有什么区别
宏任务是不同且独立的任务的集合。微任务是更新应用程序状态的次要任务,应在浏览器转到其他活动(例如重新呈现用户界面)之前完成。promise 回调和 DOM 修改更改是微任务的示例。
任务队列和调用堆栈有什么区别?
由它来检查调用堆栈是否为空以及任务队列是否有任何待处理的任务要完成。如果调用堆栈为空,它将作业从队列推送到调用堆栈,并在调用堆栈中进行处理。
调用堆栈是否类似于队列?
这种类型的堆栈通常称为执行堆栈、控制堆栈、运行时堆栈或计算机堆栈,通常缩写为“堆栈”。总之,作业队列是要完成的任务列表(通常持久维护),而调用堆栈是函数的集合。
达到最大调用堆栈大小时会发生什么情况?
当函数调用过多或函数缺少基本情况时,会发生 JavaScript 异常“太多递归”或“超出最大调用堆栈大小”
结论
在本文中,我们介绍了事件循环中的“微任务”和“宏任务”。为了总结事件循环的整个过程,我们检查以下内容:
仅当堆栈为空时,检查上面队列中发生了什么,并从堆栈的底部到顶部执行所有内容。
检查微堆栈,如有必要,在堆栈的帮助下执行所有内容,一次执行一个微任务,直到微任务队列为空或不再需要执行,然后才检查宏堆栈。
检查宏堆栈,如有必要,使用堆栈执行其中的所有内容。