为什么 JavaScript 中的微任务和(宏)任务有区别?

jam*_*mes 7 javascript event-loop

从概念上讲,对于大多数用例来说,只有一个作业队列似乎就足够了。
有多个队列并将它们区分为“微任务”和(宏)“任务”的原因是什么?

Kai*_*ido 7

拥有多个()任务队列可以实现任务优先级。

例如,用户代理(UA)可以选择将用户事件(例如点击事件)优先于网络事件,即使后者实际上首先由系统注册,因为前者可能对用户更可见并且需要更低的延迟。在 HTML 中,事件循环处理模型
的第一步允许这样做,该模型规定 UA 必须从其任务队列之一中选择要执行的下一个任务。(注意:HTML规范并不要求有多个队列,但他们确实定义了多个任务源以保证相似任务的执行顺序)。

现在,为什么还有另一个叫做微任务队列的野兽呢?

我们可以找到一些关于“如何”实现它的早期讨论,但我没有深入研究是谁首先提出这个想法以及用于什么用例。

然而,从我发现的讨论 中,我们可以看到一些需要这种机制的提案正在酝酿之中:

  • 突变观察者
  • 现已弃用的 ESObject.observe()
  • 当时传入的ES Promise,
  • 当时也传入了,但我不知道为什么它被引用了ES WeakRefs
  • HTML 自定义元素回调

前两个也是第一个在浏览器中实现的,我们可以说它们的用例是实现这种新型队列的主要原因。

两者实际上做了类似的事情:它们侦听对象(或 DOM 树)上的更改,并将作业期间发生的所有更改合并到单个事件中(不要读为事件)
有人可能会说,这个事件本来可以在下一个(宏)任务中排队,具有最高优先级,只不过事件循环已经有点复杂,而且每个作业不一定都来自tasks

例如,渲染实际上是每个事件循环迭代的一部分,只是大多数时候它会提前退出,因为不是渲染时间。
因此,如果您在渲染帧期间进行 DOM 修改,则可以渲染修改,并且只有在整个渲染发生后您才会收到回调。
由于观察者的主要用例是在观察到的更改触发性能严重副作用之前对其采取行动,我想您可以看到如何有必要在完成修改的作业之后立即插入我们的回调。


PS:当时我对事件循环了解不多,我远离规范问题,我可能在这个答案中加入了一些不合时宜的内容。