任务、微任务、队列和调度

看国外分享的,觉得写的挺好所以翻译过来,词句不够精确的欢迎指正相互学习。
外文链接:Tasks, microtasks, queues and schedules

事件循环如何处理任务和微任务

每个 线程 都有自己的事件循环,因此每个Web worker都有自己的事件循环,所以每个Web worker都可以独立运行,而同一源上的所有窗口都可以共享事件循环,因此Web worker之间是可以同步通信。事件循环持续运行,执行所有排队的任务。一个事件循环有多个任务源,这些任务源保证了源中的执行顺序(比如 IndexedDB规范 定义了它自己的执行顺序),在每一轮循环中浏览器从多个任务源中选择要执行的任务,浏览器会优先执行对性能敏感的任务,比如:用户输入。

任务是按计划执行的,浏览器可以从其内部获取任务到JavaScript/DOM域中执行,并确保这些任务按顺序进行。在任务之间,浏览器可能进行页面的渲染更新。从鼠标单击到事件回调需要调度任务,解析HTML也需要调度任务,在上例中,setTimeout同样需要调度任务。

setTimeout会在给定的时间时将回调函数放在任务中,这就是为什么setTimeout在script end之后打印,因为script end是在第一个任务中,setTimeout则在单独的任务中。
微任务会插入到当前正在执行的任务中,例如reacting to a batch of actions, or to make something async,当前执行的JavaScript中间没有其它的JavaScript要执行的话,微任务队列就会在回调函数执行完之后开始执行,在微任务执行期间 排队的任何其他微任务都会添加到微任务队列的末尾,并依次进行处理。mutation observer callbacks属于微任务,比如上面的 promise callbacks。
一旦一个承诺达成或者已经解决,它就会为它的回调排起一个微任务。这确保了即使承诺已经解决,承诺回调依然是异步的。因此当在promise后面调用.then(...)时就会立即排起一个微任务,这就是为什么promise1 和promise2 在 script end之后打印,因为当前运行的脚本必须在处理微任务之前完成,promise1 和 promise2 在 setTimeout 之前打印,因为微任务总是在下一个任务 开始执行 之前执行

结束
结束
当前执行的任务
微任务
下一个任务

这是一个演示步骤的链接,你可以点击箭头一步一步执行

是不是清晰很多

浏览器之间的差异

有些浏览器会打印script start, script end, setTimeout, promise1, promise2。他们在setTimeout之后调用promise回调。它们可能认为承诺回调是一个新任务而不是一个微任务。这是可原谅的,因为承诺来自ECMASScript而不是HTML. ECMAScript。ECMASScript具有与微任务相似的"工作"概念,但是普遍认为承诺应是微任务。

将承诺视为任务会导致性能问题,因为回调可能会被其它任务(如页面渲染)不必要地延迟。与其他任务源交互也会导致非确定性,也可能会中断与其他API的交互,稍后会详细介绍。

如何判断任务和微任务

如前所述,在ECMAScript中,微任务被称为“作业”。在 step 8.a of PerformPromiseThen 步骤中,调用EnqueueJob方法对微任务进行排队。

下面看一个更复杂的例子:

test.html:

<div class="outer">
  <div class="inner"></div>
</div>

test.js:

// Let's get hold of those elements
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');

// Let's listen for attribute changes on the
// outer element
new MutationObserver(function() {
  console.log('mutate');
}).observe(outer, {
  attributes: true
});

// Here's a click listener…
function onClick() {
  console.log('click');

  setTimeout(function() {
    console.log('timeout');
  }, 0);

  Promise.resolve().then(function() {
    console.log('promise');
  });

  outer.setAttribute('data-random', Math.random());
}

// …which we'll attach to both elements
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);

单击 div.internal想想会打印什么?在不同的浏览器里面打印结果不同。
在这里插入图片描述
到底谁对呢?

派发 点击事件 是一个任务,Mutation observer 和 promise回调 作为微任务排队,setTimeout回调 作为任务 排队。这是一个演示步骤的链接,你可以点击箭头一步一步执行
所以Chrome是对的,微任务是在回调之后处理的(只要中间没有其它JavaScript在执行),它仅限于任务结束。
使用上面相同的示例,如果我们在代码中执行:inner.click();这次我们是使用脚本来派发点击事件而非真正的交互。
不同浏览器中的打印如下:
在这里插入图片描述
在代码中的执行步骤查看演示
因此Chrome是对的。
和之前点击页面交互对比:之前的微任务在事件回调函数之间运行,但是改成在代码中运行.click()方法会导致事件派发是同步的,所以调用.click()方法的script会一直在栈中,这个规则确保微任务不会打断中间执行的JavaScript,这意味着微任务不会再事件回调中途处理,微任务会在事件回调都执行完之后再执行。

总结:

  • 任务按顺序执行,浏览器在任务执行的过程中进行渲染。
  • 微任务按顺序执行,在每次回调之后,每个任务结束时执行(如果没有其它JavaScript在执行过程中 as long as no other JavaScript is mid-execution)

最后,墙裂建议看下上面链接的演示动画,它会让你更清晰。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值