精读《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 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);

点击 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 许可证)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
未来社区的建设背景和需求分析指出,随着智能经济、大数据、人工智能、物联网、区块链、云计算等技术的发展,社区服务正朝着数字化、智能化转型。社区服务渠道由分散向统一融合转变,服务内容由通用庞杂向个性化、服务导向转变。未来社区将构建数字化生态,实现数据在线、组织在线、服务在线、产品智能和决策智能,赋能企业创新,同时注重人才培养和科研平台建设。 规划设计方面,未来社区将基于居民需求,打造以服务为中心的社区管理模式。通过统一的服务平台和应用,实现服务内容的整合和优化,提供灵活多样的服务方式,如推送式、订阅式、热点式等。社区将构建数据与应用的良性循环,提高服务效率,同时注重生态优美、绿色低碳、社会和谐,以实现幸福民生和产业发展。 建设运营上,未来社区强调科学规划、以人为本,创新引领、重点突破,统筹推进、整体提升。通过实施院落+社团自治工程,转变政府职能,深化社区自治法制化、信息化,解决社区治理中的重点问题。目标是培养有活力的社会组织,提高社区居民参与度和满意度,实现社区治理服务的制度机制创新。 未来社区的数字化解决方案包括信息发布系统、服务系统和管理系统。信息发布系统涵盖公共服务类和社会化服务类信息,提供政策宣传、家政服务、健康医疗咨询等功能。服务系统功能需求包括办事指南、公共服务、社区工作参与互动等,旨在提高社区服务能力。管理系统功能需求则涉及院落管理、社团管理、社工队伍管理等,以实现社区治理的现代化。 最后,未来社区建设注重整合政府、社会组织、企业等多方资源,以提高社区服务的效率和质量。通过建立社区管理服务综合信息平台,提供社区公共服务、社区社会组织管理服务和社区便民服务,实现管理精简、高效、透明,服务快速、便捷。同时,通过培育和发展社区协会、社团等组织,激发社会化组织活力,为居民提供综合性的咨询和服务,促进社区的和谐发展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值