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

Why is there a distinction between microtask and (macro)task in JavaScript?

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

拥有多个()任务队列可以进行任务优先级排序。

例如,用户代理 (UA) 可以选择将用户事件(例如点击事件)优先于网络事件,即使后者实际上首先由系统注册,因为前者可能更重要对用户可见并且需要较低的延迟。
在 HTML 中,Event Loop's Processing model 的第一步允许这样做,它指出 UA 必须从其任务队列中选择下一个要执行的任务。
(注:HTML规范不要求有多个queue,但确实定义了多个task sources来保证类似的执行顺序任务).

现在,为什么还有一个名为 microtask queue 的野兽?

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

然而,从the discussions I found,我们可以看到一些需要这种机制的提案正在路上:

  • 变异观察者
  • 现已弃用 ES Object.observe()
  • 当时收到 ES Promises,
  • 同时传入,我不知道为什么它被引用 ES WeakRefs
  • HTML 自定义元素回调

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

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

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


PS:当时我对事件循环知之甚少,我对规范问题还很遥远,我可能在这个答案中加入了一些不合时宜的内容。