事件循环上下文中微任务和宏任务的区别

我刚刚读完Promises/A+规范,无意中发现了术语微任务和宏任务:参见http://promisesaplus.com/#notes

我以前从未听说过这些术语,现在我很好奇会有什么不同?

我已经试着在网上找到一些信息,但我只找到了w3.org档案中的这篇文章(这并没有向我解释区别):http://lists.w3.org/Archives/Public/public-nextweb/2013Jul/0018.html

此外,我还找到了一个npm模块,名为“macrotask”:https://www.npmjs.org/package/macrotask 同样,没有明确的区别到底是什么

我所知道的是,它与事件循环有关,如https://html.spec.whatwg.org/multipage/webappapis.html#task-queue所述https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint < / p >

我知道,根据WHATWG规范,理论上我应该能够自己提取差异。但我相信其他人也能从专家给出的简短解释中受益。

87199 次浏览
事件循环的一次循环将从macrotask队列处理一个任务(此队列在WHATWG规范中简单地称为任务队列)。 在这个宏任务完成后,所有可用的microtasks将被处理,即在同一个循环周期内。当这些微任务被处理时,它们可以排队更多的微任务,这些微任务将一个接一个地运行,直到微任务队列耗尽

这样做的实际后果是什么?

如果microtask递归地排队其他微任务,它可能需要很长时间才能处理下一个宏任务。这意味着,您最终可能会遇到阻塞的UI,或者应用程序中一些已完成的I/O处于空闲状态。

但是,至少对于Node.js的进程。nextTick函数(排队microtasks),通过process.maxTickDepth有一个内置的保护来防止这种阻塞。此值被设置为默认值1000,在达到此限制后减少对microtasks的进一步处理,从而允许处理下一个macrotask)

那么什么时候用什么呢?

基本上,当你需要以同步方式异步地做事情时(即当你说在最近的将来执行这个(微)任务时),使用microtasks。 否则,坚持macrotasks

例子

< p > macrotasks: setTimeoutsetInterval setImmediate, requestAnimationFrame I / O, UI呈现< br > 微任务: process.nextTick承诺queueMicrotaskMutationObserver

规范中的基本概念:

  • 一个事件循环有一个或多个任务队列。(任务队列为宏任务队列)
  • 每个事件循环都有一个微任务队列。
  • 任务队列=宏任务队列!=微任务队列
  • 一个任务可以被推入宏任务队列,也可以被推入微任务队列
  • 当一个任务被推入队列(微观/宏观)时,我们意味着准备工作已经完成,所以任务现在可以执行了。

事件循环过程模型如下:

调用堆栈为空时,执行步骤-

  1. 选择任务队列中最老的任务(任务A)
  2. 如果任务A为空(意味着任务队列为空),则跳转到步骤6
  3. 将“当前正在运行的任务”设置为“任务A”
  4. 运行“任务A”(意味着运行回调函数)
  5. 将“当前运行的任务”设为空,删除“任务A”
  6. 执行微任务队列
    • (a).选择微任务队列中最老的任务(任务x)
    • (b)如果任务x为空(意味着微任务队列为空),跳转到步骤(g)
    • (c).将“当前运行的任务”设置为“任务x”
    • (d).运行“任务x”
    • (e).将“当前运行的任务”设为空,删除“任务x”
    • (f).选择微任务队列中最老的任务,跳转到步骤(b)
    • (g).完成微任务队列;
    • 李< / ul > < / >
    • 跳转到步骤1。

简化后的过程模型如下:

  1. 运行宏任务队列中最老的任务,然后删除它。
  2. 运行微任务队列中所有可用的任务,然后删除它们。
  3. 下一轮:在宏任务队列中运行下一个任务(跳过步骤2)

需要记住的事情:

  1. 当一个任务(在宏任务队列中)正在运行时,可能会注册新的事件。因此可能会创建新的任务。下面是两个新创建的任务:
    • promise .then()的回调是一个任务
      • 在当前的事件循环中,任务将被推入微任务队列。
      • 在事件循环的下一轮(可能是下一轮),任务将被推入微任务队列。
      • 李< / ul > < / >
      • setTimeout(callback,n)的回调是一个任务,将被推入宏任务队列,即使n为0;
      • 李< / ul > < / >
      • 微任务队列中的任务将在当前一轮运行,而宏任务队列中的任务将等待下一轮事件循环。
      • 我们都知道callback的"click","scroll","ajax","setTimeout"…是任务,但是我们也应该记住js代码作为一个整体在脚本标签是一个任务(一个宏任务)太。

我认为我们不能从堆栈中分离出来讨论事件循环,所以:

JS有三个“堆栈”:

  • 所有同步调用的标准堆栈(一个函数调用另一个函数,等等)
  • (过程。nextTick,承诺,对象。观察,MutationObserver)用于所有具有更高优先级的异步操作的微任务队列(或作业队列或微任务堆栈)
  • (setTimeout, setInterval, setimmediation, requestAnimationFrame, I/O, UI渲染)用于所有低优先级的异步操作的宏任务队列(或事件队列,任务队列,宏任务队列)
|=======|
| macro |
| [...] |
|       |
|=======|
| micro |
| [...] |
|       |
|=======|
| stack |
| [...] |
|       |
|=======|

事件循环是这样工作的:

  • 执行栈中从下到上的所有操作,并且只有当栈为空时,才检查上面的队列中发生了什么
  • 检查微堆栈并在堆栈的帮助下执行那里的所有内容(如果需要),一个微任务接一个微任务,直到微任务队列为空或不需要任何执行,然后才检查宏堆栈
  • 检查宏堆栈并在堆栈的帮助下执行其中的所有内容(如果需要)

如果栈不为空,微栈将不会被触动。如果微堆栈不为空或不需要任何执行,宏堆栈将不会被触及。

综上所述:微任务队列与宏任务队列几乎相同,但这些任务(过程。nextTick,承诺,对象。观察,MutationObserver)的优先级高于宏任务。

微观就像宏观,但优先级更高。

这里有“ultimate"理解一切的代码。


console.log('stack [1]');
setTimeout(() => console.log("macro [2]"), 0);
setTimeout(() => console.log("macro [3]"), 1);


const p = Promise.resolve();
for(let i = 0; i < 3; i++) p.then(() => {
setTimeout(() => {
console.log('stack [4]')
setTimeout(() => console.log("macro [5]"), 0);
p.then(() => console.log('micro [6]'));
}, 0);
console.log("stack [7]");
});


console.log("macro [8]");
/ *结果: 堆栈[1] 宏[8] 堆叠[7],堆叠[7],堆叠[7] 宏[2] 宏[3] 堆栈[4] 微[6] 堆栈[4] 微[6] 堆栈[4] 微[6] 宏[5]宏[5]宏[5] -------------------- 但在节点在版本<11(旧版本)你会得到一些不同的东西 堆栈[1] 宏[8] 堆叠[7],堆叠[7],堆叠[7] 宏[2] 宏[3] 堆叠[4],堆叠[4],堆叠[4] 微[6]微[6]微[6] 宏[5]宏[5]宏[5] 更多信息:https://blog.insiderattack.net/new-changes-to-timers-and-microtasks-from-node-v11-0-0-and-above-68d112743eb3 * / < /代码> < / >之前

JavaScript是高级的、单线程语言、解释语言。这意味着它需要一个解释器来将JS代码转换为机器代码。解释器是指引擎。chrome的V8引擎和safari的webkit。 每个引擎都包含内存、调用堆栈、事件循环、计时器、web API、事件等

事件循环:微任务和宏任务

事件循环的概念非常简单。这是一个无休止的循环,JavaScript引擎等待任务,执行它们,然后休眠,等待更多的任务

任务设置-引擎处理它们-然后等待更多的任务(在睡眠和消耗接近零CPU时)。 可能发生的情况是,当引擎繁忙时,一个任务来了,然后它进入队列。 这些任务形成一个队列,称为“macrotask队列

Microtasks完全来自我们的代码。它们通常由promise创建:.then/catch/finally处理程序的执行变成了一个微任务。微任务也在await的“掩护下”使用,因为它是另一种形式的承诺处理。 在每个宏任务之后,引擎立即执行微任务队列中的所有任务,在运行任何其他宏任务或渲染或其他任何

之前

enter image description here

宏任务包括键盘事件、鼠标事件、定时器事件(setTimeout)、网络事件、Html解析、修改Urletc。宏任务表示一些离散且独立的工作。微任务队列具有较高的优先级,宏任务将等待所有的微任务先执行。

微任务,是更新应用程序状态的较小任务,应该在浏览器继续执行其他分配之前执行 重新渲染UI。微任务包括承诺回调和DOM突变更改。微任务使我们能够在UI重新渲染之前执行某些操作,从而避免不必要的UI渲染,可能会显示不一致的应用程序状态

宏、微任务分离 事件循环优先级的任务类型;

.

.

. 在单个循环迭代中,最多处理一个宏任务 (其他任务留在队列中等待),而所有的微任务都被处理
  • 两个任务队列都放置在事件循环之外,以指示将任务添加到匹配队列的行为发生在事件循环之外 事件循环。否则,JavaScript代码时发生的任何事件 执行时将被忽略。检测和添加的行为

  • 两种任务都是一次执行一个。当一个任务开始执行时,它将被执行到完成。只有浏览器可以停止 任务的执行;例如,如果任务占用太多时间

    .时间或记忆
  • 所有微任务都应该在下一次呈现之前执行,因为它们的目标是在呈现发生之前更新应用程序状态。

浏览器通常尝试每秒呈现页面60次, 人们普遍认为60帧每秒是 动画将显示平滑。如果我们想要实现平稳运行 应用程序、单个任务以及由该任务生成的所有微任务 理想情况下应在16毫秒内完成。如果一个任务执行了更多 几秒钟后,浏览器显示“无响应脚本” 消息。< / p >

reference John Resig-secrets of JS Ninja

我根据4个概念创建了一个事件循环伪代码:

  1. setTimeout, setInterval, setimmediation, requestAnimationFrame, I/O, UI渲染是宏任务队列的一部分。首先将处理一个宏任务项。

  2. 的过程。nextTick,承诺,对象。观察,MutationObserver是微任务队列的一部分。事件循环将处理该队列中的所有项,包括在当前迭代期间处理的一次。

  3. 还有一个队列叫做动画队列,它保存着接下来要处理的动画更改任务项。该队列中存在的所有任务都将被处理(不包括在当前迭代中添加的新任务)。如果是渲染时间,它将被调用

  4. 渲染管道将尝试每秒渲染60次(每16毫秒)

     while (true){
    // 1. Get one macrotask (oldest) task item
    task = macroTaskQueue.pop();
    execute(task);
    
    
    // 2. Go and execute microtasks while they have items in their queue (including those which were added during this iteration)
    while (microtaskQueue.hasTasks()){
    const microTask = microtaskQueue.pop();
    execute(microTask);
    }
    
    
    // 3. If 16ms have elapsed since last time this condition was true
    if (isPaintTime()){
    // 4. Go and execute animationTasks while they have items in their queue (not including those which were added during this iteration)
    const animationTasks = animationQueue.getTasks();
    for (task in animationTasks){
    execute(task);
    }
    
    
    repaint(); // render the page changes (via the render pipeline)
    }
    }