乐趣区

关于javascript:精读Tasks-microtasks-queues-and-schedules

1 引言

本周跟着 Tasks, microtasks, queues and schedules 这篇文章一起深刻了解这些概念间的区别。

先说论断:

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

    • 如果没有执行中的 js 堆栈,则在每个回调之后。
    • 在每个 task 之后。

2 概述

Event Loop

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

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

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。

点击冒泡 + 工作

上面给出了更简单的例子,提前阐明前面的例子 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 许可证)

退出移动版