精读《Tasks, microtasks, queues and schedules》

1 引言

本周跟着 Tasks, microtasks, queues and schedules 这篇文章一块儿深刻理解这些概念间的区别。前端

先说结论:git

  • Tasks 按顺序执行,浏览器可能在 Tasks 之间执行渲染。
  • Microtasks 也按顺序执行,时机是:github

    • 若是没有执行中的 js 堆栈,则在每一个回调以后。
    • 在每一个 task 以后。

2 概述

Event Loop

在说这些概念前,先要介绍 Event Loop。编程

首先浏览器是多线程的,每一个 JS 脚本都在单线程中执行,每一个线程都有本身的 Event Loop,同源的全部浏览器窗口共享一个 Event Loop 以便通讯。promise

Event Loop 会持续循环的执行全部排队中的任务,浏览器会为这些任务划分优先级,按照优先级来执行,这就会致使 Tasks 与 Microtasks 执行顺序与调用顺序的不一样。浏览器

promise 与 setTimeout

看下面代码的输出顺序:微信

console.log("script start");setTimeout(function () {  console.log("setTimeout");}, 0);Promise.resolve()  .then(function () {    console.log("promise1");  })  .then(function () {    console.log("promise2");  });console.log("script end");

正确答案是 script start, script end, promise1, promise2, setTimeout,在线程中,同步脚本执行优先级最高,而后 promise 任务会存放到 Microtasks,setTimeout 任务会存放到 Tasks,Microtasks 会优先于 Tasks 执行。多线程

Microtasks 中文能够翻译为微任务,只要有 Microtasks 插入,就会不断执行 Microtasks 队列直到结束,在结束前都不会执行到 Tasks。dom

点击冒泡 + 任务

下面给出了更复杂的例子,提早说明后面的例子 Chrome、Firefox、Safari、Edge 浏览器的结果彻底不同,但只有 Chrome 的运行结果是对的!为何 Chrome 是对的呢,请看下面的分析:异步

<div class="outer">  <div class="inner"></div></div>
// Let's get hold of those elementsvar outer = document.querySelector(".outer");var inner = document.querySelector(".inner");// Let's listen for attribute changes on the// outer elementnew 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 elementsinner.addEventListener("click", onClick);outer.addEventListener("click", onClick);

点击 inner 区块后,正确输出顺序应该是:

click
promise
mutate
click
promise
mutate
timeout
timeout

逻辑以下:

  1. 点击触发 onClick 函数入栈。
  2. 当即执行 console.log('click') 打印 click
  3. console.log('timeout') 入栈 Tasks。
  4. console.log('promise') 入栈 microtasks。
  5. outer.setAttribute('data-random') 的触发致使监听者 MutationObserver 入栈 microtasks。
  6. onClick 函数执行完毕,此时线程调用栈为空,开始执行 microtasks 队列。
  7. 打印 promise,打印 mutate,此时 microtasks 已空。
  8. 执行冒泡机制,outer div 也触发 onClick 函数,同理,打印 promise,打印 mutate
  9. 都执行完后,执行 Tasks,打印 timeout,打印 timeout

模拟点击冒泡 + 任务

若是将触发 onClick 行为由点击改成:

inner.click();

结果会不一样吗?答案是会(单元测试与用户行为不符合,单测也有无解的时候)。然而四大浏览器的执行结果也是彻底不同,但从逻辑上讲仍然 Chrome 是对的,让咱们看下 Chrome 的结果:

click
click
promise
mutate
promise
timeout
timeout

逻辑以下:

  1. inner.click() 触发 onClick 函数入栈。
  2. 当即执行 console.log('click') 打印 click
  3. console.log('timeout') 入栈 Tasks。
  4. console.log('promise') 入栈 microtasks。
  5. outer.setAttribute('data-random') 的触发致使监听者 MutationObserver 入栈 microtasks。
  6. 因为冒泡改成 js 调用栈执行,因此此时 js 调用栈未结束,不会执行 microtasks,反而是继续执行冒泡,outer 的 onClick 函数入栈。
  7. 当即执行 console.log('click') 打印 click
  8. console.log('timeout') 入栈 Tasks。
  9. console.log('promise') 入栈 microtasks。
  10. MutationObserver 因为还没调用,所以此次 outer.setAttribute('data-random') 的改动实际上没有做用。
  11. js 调用栈执行完毕,开始执行 microtasks,按照入栈顺序,打印 promisemutatepromise
  12. microtasks 执行完毕,开始执行 Tasks,打印 timeouttimeout

3 精读

基于任务调度这么复杂,且浏览器实现方式很不一样,下面两件事是我很不推荐的:

  1. 业务逻辑 “巧妙” 依赖了 microtasks 与 Tasks 执行逻辑的微妙差别。
  2. 死记硬背调用顺序。

且不说依赖了调用顺序的业务逻辑自己就很难维护,不一样浏览器之间对任务调用顺序仍是不一样的,这可能源于对 W3C 标准规范理解的误差,也多是 BUG,这会致使依赖于此的逻辑很是脆弱。

虽然上面两个例子很是复杂,但咱们也没必要把这个例子看成经典背诵,只要记住文章开头提到的执行逻辑就能够推导:

  • Tasks 按顺序执行,浏览器可能在 Tasks 之间执行渲染。
  • Microtasks 也按顺序执行,时机是:

    • 若是没有执行中的 js 堆栈,则在每一个回调以后。
    • 在每一个 task 以后。

记住 PromiseMicrotaskssetTimeoutTasks,JS 一次 Event Loop 完毕后,即调用栈没有内容时才会执行 Microtasks -> Tasks,在执行 Microtasks 过程当中插入的 Microtasks 会按顺序继续执行,而执行 Tasks 中插入的 Microtasks 得等到调用栈执行完后才继续执行。

上面说的内容都是指一次 Event Loop 时当即执行的优先级,不要和执行延迟时间弄混淆了。

把 JS 线程的 Event Loop 看成一个函数,函数内同步逻辑执行优先级是最高的,若是遇到 MicrotasksTasks 就会当即记录下来,当一次 Event Loop 执行完后当即调用 Microtasks,等 Microtasks 队列执行完毕后可能进行一些渲染行为,等这些浏览器操做完成后,再考虑执行 Tasks 队列。

4 总结

最后,仍是要强调一句,不要依赖 MicrotasksTasks 的执行顺序,尤为在申明式编程环境中,咱们能够把 MicrotasksTasks 都看成是异步内容,在渲染时作好状态判断便可,不用关心前后顺序。

讨论地址是: 精读《Tasks, microtasks, queues and schedules》· Issue #264 · dt-fe/weekly

若是你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。

关注 前端精读微信公众号

版权声明:自由转载-非商用-非衍生-保持署名( 创意共享 3.0 许可证
相关文章
相关标签/搜索