“Event Loop:事件循环”的版本间差异

来自Wikioe
跳到导航 跳到搜索
无编辑摘要
无编辑摘要
第6行: 第6行:
  而网络上搜到的内容大同小异,互相又有不小出入,难以拿捏。
  而网络上搜到的内容大同小异,互相又有不小出入,难以拿捏。
   
   
  众所周知,JavaScript 是没有所谓源代码的“源代码”,所以这部分内容只能在“规范”文件<ref name="HTML-Standard">参见:[https://html.spec.whatwg.org/multipage/webappapis.html#event-loops 《HTML Standard》:“Event loops”一节]</ref>了。
  众所周知,JavaScript 是没有所谓源代码的“源代码”,所以这部分内容只能在“规范”文件<ref name="HTML-Standard">参考:[https://html.spec.whatwg.org/multipage/webappapis.html#event-loops 《HTML Standard》:“Event loops”一节]</ref>了。


*  
*  
第22行: 第22行:
=== JavaScript的“任务” ===
=== JavaScript的“任务” ===
  JavaScript中,“任务”被分为两种:“同步任务”,“异步任务”。“异步任务”(Task):又分为“宏任务”(MacroTask)与“微任务”(MicroTask)。
  JavaScript中,“任务”被分为两种:“同步任务”,“异步任务”。“异步任务”(Task):又分为“宏任务”(MacroTask)与“微任务”(MicroTask)。
Event Loop 是针对“异步任务”而言。


# 同步任务:在“主线程”上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
# 同步任务:在“主线程”上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
第27行: 第29行:
## '''宏任务(macrotask)''':
## '''宏任务(macrotask)''':
## '''微任务(microtask)''':
## '''微任务(microtask)''':




第38行: 第39行:




== Event Loop ==
== Event Loop<ref name="HTML-Standard"/> ==
  要协调事件(event),用户交互(user interaction),脚本(script),渲染(rendering),网络(networking)等,用户代理必须使用本节中描述的事件循环。
  要协调事件(event),用户交互(user interaction),脚本(script),渲染(rendering),网络(networking)等,用户代理必须使用本节中描述的事件循环。


每个“代理”(Agent<ref>
每个“代理”(Agent<ref>
<blockquote>
<blockquote>
“代理”(Agent)包括一组:ECMAScript执行上下文、执行上下文堆栈、运行的执行上下文、代理记录和执行线程。除了执行线程之外,代理的组成部分只属于该代理。
“代理”(Agent)包括一组:ECMAScript执行上下文、执行上下文堆栈、运行的执行上下文、代理记录和执行线程。除了执行线程之外,代理的组成部分只属于该代理。 ——《ECMAScript® 2023 Language Specification》
</blockquote>
<blockquote>
从概念上讲,代理概念是一个独立于体系结构、理想化的“线程”,JavaScript代码在其中运行。 ——《HTML Standard》
</blockquote>
<blockquote>
每个代理由一组执行上下文的集合、执行上下文栈、主线程、一组可能创建用于执行 worker 的额外的线程集合、一个任务队列以及一个微任务队列构成。除了主线程(某些浏览器在多个代理之间共享的主线程)之外,其他组成部分对该代理都是唯一的。 ——MDN:《深入:微任务与 Javascript 运行时环境》
</blockquote>
</blockquote>
从概念上讲,代理概念是一个独立于体系结构、理想化的“线程”,JavaScript代码在其中运行。
* 一个宿主(浏览器、Node.js)可以有多个 Agent,当两个 Agent 同处一个“Agent Clusters”(代理集群)时,它们可以共享内存。
* 一个宿主(浏览器、Node.js)可以有多个 Agent,当两个 Agent 同处一个“Agent Clusters”(代理集群)时,它们可以共享内存。
* Agent 之间相互隔离,可以通过发送消息进行通信。
* Agent 之间相互隔离,可以通过发送消息进行通信。
第51行: 第57行:
* 每个 ECMAScript 程序都必须在一个名为 Agent 的区域内执行。
* 每个 ECMAScript 程序都必须在一个名为 Agent 的区域内执行。
参考:
参考:
# [https://tc39.es/ecma262/#sec-agents 《ECMAScript® 2023 Language Specification》:“Agents”一节]
# [https://html.spec.whatwg.org/multipage/webappapis.html#agents-and-agent-clusters 《HTML Standard》:“Agents and agent clusters”一节]
# [https://html.spec.whatwg.org/multipage/webappapis.html#agents-and-agent-clusters 《HTML Standard》:“Agents and agent clusters”一节]
# [https://tc39.es/ecma262/#sec-agents 《ECMAScript® 2023 Language Specification》:“Agents”一节]
# [https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_DOM_API/Microtask_guide/In_depth MDN:《深入:微任务与 Javascript 运行时环境》]
# [https://github.com/Lawliet01/everyone-can-read-spec/blob/main/8.execution-environment.md 【人人都能读标准】8.可视化JavaScript的运行环境:agents、执行上下文、Realm]
# [https://github.com/Lawliet01/everyone-can-read-spec/blob/main/8.execution-environment.md 【人人都能读标准】8.可视化JavaScript的运行环境:agents、执行上下文、Realm]
</ref>)有一个关联的“事件循环”,且该循环对该代理是'''唯一'''的。
</ref>)有一个关联的“事件循环”,且该循环对该代理是'''唯一'''的。
第61行: 第68行:




* 一个事件循环具有'''一个或多个“任务队列”(task queues)'''。
* 一个事件循环具有<span style="color: Green">'''一个或多个“任务队列”(task queues)'''</span>
** <q>'''任务队列是“集合”(set),而不是“队列”(queue)''',因为事件循环处理模型从所选队列中获取第一个可运行的任务,而不是将第一个任务出列。</q>
** <q>'''任务队列是“集合”(set),而不是“队列”(queue)''',因为事件循环处理模型从所选队列中获取第一个可运行的任务,而不是将第一个任务出列。</q>
** <q>'''微任务队列(microtask queue)不是任务队列(task queues)'''。</q>
** <q>'''微任务队列(microtask queue)不是任务队列(task queues)'''。</q>
* 每个事件循环都有一个当前“正在运行的任务”(currently running task),要么是一个任务,要么是 null。初始为空。
* 每个事件循环都有一个当前“正在运行的任务”(currently running task),要么是一个任务,要么是 null。初始为空。
** 它用于处理可重入性。
** 它用于处理可重入性。
* 每个事件循环都有'''一个“微任务队列”(microtask queue)''',该队列是一个初始为空的微任务队列。
* 每个事件循环都有<span style="color: Green">'''一个“微任务队列”(microtask queue)'''</span>,该队列是一个初始为空的微任务队列。
* 每个事件循环都有一个“正在执行微任务的检查点”(performing a microtask checkpoint)的布尔值。该值最初为 false。
* 每个事件循环都有一个“正在执行微任务的检查点”(performing a microtask checkpoint)的布尔值。该值最初为 false。
** 它用于防止重入调用。
** 它用于防止重入调用。
第74行: 第81行:
=== 任务(Tasks) ===
=== 任务(Tasks) ===
“任务”(Tasks)包括以下几种:
“任务”(Tasks)包括以下几种:
# 事件(Events):在特定 EventTarget 对象上调度 Event 对象,通常由专用的任务完成。
# '''事件'''(Events):在特定 EventTarget 对象上调度 Event 对象,通常由专用的任务完成。
# 解析(Parsing):HTML 解析器标记一个或多个字节,然后处理任何生成的标记,通常是一项任务。
# '''解析'''(Parsing):HTML 解析器标记一个或多个字节,然后处理任何生成的标记,通常是一项任务。
# 回调(Callbacks):调用回调通常由一个专用的任务完成。
# '''回调'''(Callbacks):调用回调通常由一个专用的任务完成。
# 使用资源(Using a resource):当算法获取资源时,如果获取是以非阻塞的方式发生的,那么一旦部分或全部资源可用,就由任务执行对资源的处理。
# '''使用资源'''(Using a resource):当算法获取资源时,如果获取是以非阻塞的方式发生的,那么一旦部分或全部资源可用,就由任务执行对资源的处理。
# 对 DOM 操作做出反应(Reacting to DOM manipulation):一些元素具有响应DOM操作而触发的任务,例如:当该元素被插入到文档中时。
# '''对 DOM 操作做出反应'''(Reacting to DOM manipulation):一些元素具有响应 DOM 操作而触发的任务,例如:当该元素被插入到文档中时。




从形式上讲,“任务”(Tasks)具有以下结构:
从形式上讲,“任务”(Tasks)具有以下结构:
# 步骤(steps):指定任务要完成的工作的一系列步骤。
# 步骤(steps):指定任务要完成的工作的一系列步骤。
# 来源(source):用于对相关任务进行分组和序列化。
# '''来源'''(source):用于对相关任务进行分组和序列化。
# 文档(document)与任务关联的文档,对于不在“窗口事件循环”中的任务则为 null。
# 文档(document)与任务关联的文档,对于不在“窗口事件循环”中的任务则为 null。
# 脚本评估环境设置对象集(A script evaluation environment settings object set):用于跟踪任务期间的脚本评估。
# 脚本评估环境设置对象集(A script evaluation environment settings object set):用于跟踪任务期间的脚本评估。
根据其源字段(source),每个任务都被定义为来自特定的“任务源”(task source)。对于每个事件循环,每个“任务源”都必须与特定的“任务队列”相关联。
根据其源字段(source),每个任务都被定义为来自特定的“任务源”(task source)。对于每个事件循环,每个“任务源”都必须与特定的“任务队列”相关联。
=== 通用任务源(task sources) ===
# DOM 操作任务源(The DOM manipulation task source):用于对 DOM 操作做出反应的功能,例如在将元素插入文档时以非阻塞方式发生的事情。
# 用户交互任务源(The user interaction task source):用于对用户交互做出反应的功能,例如键盘或鼠标输入。
# 网络任务源(The networking task source):用于响应网络活动而触发的功能。
# 导航和遍历任务源(The navigation and traversal task source):用于对导航和历史遍历中涉及的任务进行排队。


=== '''处理模型''' ===
=== '''处理模型''' ===
第92行: 第105行:
# 让 oldestTask 和 taskStartTime 为 null。
# 让 oldestTask 和 taskStartTime 为 null。
# 如果事件循环有一个'''任务队列'''其中至少有一个可运行的任务,则:
# 如果事件循环有一个'''任务队列'''其中至少有一个可运行的任务,则:
## 由实现(浏览器)定义的规则,选择一个任务队列作为 taskQueue。(“微任务队列”并不是一个“任务队列”,所以不会被选择)
## 由“实现”(浏览器)定义的规则,选择一个任务队列作为 taskQueue。(“微任务队列”并不是一个“任务队列”,所以不会被选择)——【“选择哪个任务队列”:由浏览器定义(即文档中的“implementation-defined”,客户端软件本身被称为一种“implementation”)决定,这就允许优先选择对性能敏感的任务】
## 将 taskStartTime 设置为“不安全的共享当前时间”。
## 将 taskStartTime 设置为“不安全的共享当前时间”。
## 将 oldestTask 设置为 taskQueue 中的'''第一个可运行任务''',并将其从 taskQueue 中删除。——【任务队列是“set”,所以并非“出队”】
## 将 oldestTask 设置为 taskQueue 中的'''第一个可运行任务''',并将其从 taskQueue 中删除。——【任务队列是“set”,所以并非“出队”】
第120行: 第133行:


=== 总结 ===
=== 总结 ===
Event Loop 执行任务的顺序为:<span style="color: blue">'''任务队列的“第一个可运行任务”(宏任务) -> 微任务队列的所有任务(微任务) -> 渲染'''</span>
对于说法:“微任务 -> 渲染 -> 宏任务”,从代码运行结果来看似乎没问题,其实忽略了一点“'''script 脚本本身也是一个任务(宏任务)'''”。


== 补充内容 ==
=== 到底什么是“Microtasks”? ===
无论是《ECMAScript® 2023 Language Specification》、《HTML Standard》、MDN,都没有找到关于“宏任务”、“微任务”的定义性描述。
——《ECMAScript® 2023 Language Specification》:压根就没提到 Microtasks 这个字
——《HTML Standard》:提到了 Microtask queuing:
    A microtask is a colloquial way of referring to a task that was created via the queue a microtask algorithm.
    微任务是一种口语化的方式,指的是通过“排队微任务算法”创建的任务。 ——【但文档中,与“任务”相比似乎只有“任务源”(task source)不同】
—— MDN:只提到了“任务”(Tasks)与“微任务”(Microtasks)的区别
此外,在 Jake Archibald 的文章<ref>参考:[https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ 《Tasks, microtasks, queues and schedules》——Jake Archibald]</ref>中提到:
    ECMAScript has the concept of "jobs" which are similar to microtasks, but the relationship isn't explicit aside from vague mailing list discussions. However, the general consensus is that promises should be part of the microtask queue, and for good reason.
    ECMAScript有类似于 microtasks 的“jobs”概念,但除了模糊的邮件列表讨论之外,这种关系并不明确。然而,普遍的共识是,promises 应该是 microtask queue 的一部分,这是有充分理由的。
    ……
    As mentioned, in ECMAScript land, they call microtasks "jobs". In step 8.a of PerformPromiseThen, EnqueueJob is called to queue a microtask.
    如前所述,在 ECMAScript 领域,他们将微任务称为“jobs”。在 PerformPromiseThen 的步骤 8.a 中,调用 EnqueueJob 对 microtask 进行排队。
再此外,javascript.info 中有提到:
    Microtasks come solely from our code. They are usually created by promises……<ref>参考:[https://javascript.info/event-loop javascript.info:《Event loop: microtasks and macrotasks》]</ref>
    微任务仅来自于我们的代码。它们通常是由 promise 创建的……
   
    Asynchronous tasks need proper management. For that, the ECMA standard specifies an internal queue PromiseJobs, more often referred to as the “microtask queue” (V8 term).<ref>参考:[https://javascript.info/microtask-queue javascript.info:《Microtasks》]</ref>
    异步任务需要适当的管理。为此,ECMA 标准规定了一个内部队列 PromiseJobs,通常被称为“微任务队列(microtask queue)”(V8 术语)。


当宏任务和微任务都处于 任务队列(Task Queue) 中时,微任务的优先级大于宏任务,即先将微任务执行完,再执行宏任务;
所以,Promise 异步任务(注意,仅仅是它的回调,而不是它本身)是 microtask 应该是“潜规则”了,此外,我们还可以使用“'''queueMicrotask()'''”方法来添加 microtask。
这是理解上优点小偏差
: ——【P.S. 对于 Node.js 并不清楚】


 
=== 如何添加 microtask? ===
== 补充内容 ==
=== 如何使用“微任务”(microtask)? ===





2023年3月24日 (五) 09:54的版本


关于

在 MDN 上看到 JavaScript 的《并发模型与事件循环》一节,但由于该页面内容过少,实在不变学习。

而网络上搜到的内容大同小异,互相又有不小出入,难以拿捏。

众所周知,JavaScript 是没有所谓源代码的“源代码”,所以这部分内容只能在“规范”文件[1]了。
  • 以下内容参照《HTML Standard》、MDN、与网络内容整理。

前置知识

见:浏览器基础:进程与线程

“一个 Tab 页面对应一个渲染进程”,而“渲染进程中只能存在一个 JS 引擎线程”(即,主线程),即:页面中的所有 JS 都由同一单线程运行。

??????由于“渲染进程”负责了页面几乎所有的任务(事件、交互、脚本、渲染、网络),所以如何协调各个任务的执行是关键问题。

浏览器的进程与线程.png

JavaScript的“任务”

JavaScript中,“任务”被分为两种:“同步任务”,“异步任务”。“异步任务”(Task):又分为“宏任务”(MacroTask)与“微任务”(MicroTask)。

Event Loop 是针对“异步任务”而言。
  1. 同步任务:在“主线程”上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
  2. 异步任务:不直接进入“主线程”,而进入“任务队列”(task queue)的任务。
    1. 宏任务(macrotask)
    2. 微任务(microtask)


JavaScript的“运行时”

与 Java 的运行时概念类似,JavaScript 也有其运行时。



Event Loop[1]

要协调事件(event),用户交互(user interaction),脚本(script),渲染(rendering),网络(networking)等,用户代理必须使用本节中描述的事件循环。

每个“代理”(Agent[2])有一个关联的“事件循环”,且该循环对该代理是唯一的。

  1. Similar-origin window agent 的事件循环称为“窗口事件循环”(window event loop)。
  2. Dedicated worker agent、Shared worker agent 或 Service worker agent 的事件循环称为“工作事件循环”(worker event loop)。
  3. Worklet agent 的事件循环称为“工件事件循环”(worklet event loop)。
  • 事件循环不一定对应于实现线程。(例如,可以在单个线程中协同调度多个窗口事件循环)


  • 一个事件循环具有一个或多个“任务队列”(task queues)
    • 任务队列是“集合”(set),而不是“队列”(queue),因为事件循环处理模型从所选队列中获取第一个可运行的任务,而不是将第一个任务出列。
    • 微任务队列(microtask queue)不是任务队列(task queues)
  • 每个事件循环都有一个当前“正在运行的任务”(currently running task),要么是一个任务,要么是 null。初始为空。
    • 它用于处理可重入性。
  • 每个事件循环都有一个“微任务队列”(microtask queue),该队列是一个初始为空的微任务队列。
  • 每个事件循环都有一个“正在执行微任务的检查点”(performing a microtask checkpoint)的布尔值。该值最初为 false。
    • 它用于防止重入调用。
  • 每个窗口事件循环都有一个 DOMHighResTimeStamp 表示“上次渲染机会时间”(last render opportunity time),最初设置为零。
  • 每个窗口事件循环都有一个 DOMHighResTimeStamp 表示“上一个空闲周期的开始时间”(last idle period start time),最初设置为零。

任务(Tasks)

“任务”(Tasks)包括以下几种:

  1. 事件(Events):在特定 EventTarget 对象上调度 Event 对象,通常由专用的任务完成。
  2. 解析(Parsing):HTML 解析器标记一个或多个字节,然后处理任何生成的标记,通常是一项任务。
  3. 回调(Callbacks):调用回调通常由一个专用的任务完成。
  4. 使用资源(Using a resource):当算法获取资源时,如果获取是以非阻塞的方式发生的,那么一旦部分或全部资源可用,就由任务执行对资源的处理。
  5. 对 DOM 操作做出反应(Reacting to DOM manipulation):一些元素具有响应 DOM 操作而触发的任务,例如:当该元素被插入到文档中时。


从形式上讲,“任务”(Tasks)具有以下结构:

  1. 步骤(steps):指定任务要完成的工作的一系列步骤。
  2. 来源(source):用于对相关任务进行分组和序列化。
  3. 文档(document)与任务关联的文档,对于不在“窗口事件循环”中的任务则为 null。
  4. 脚本评估环境设置对象集(A script evaluation environment settings object set):用于跟踪任务期间的脚本评估。

根据其源字段(source),每个任务都被定义为来自特定的“任务源”(task source)。对于每个事件循环,每个“任务源”都必须与特定的“任务队列”相关联。

通用任务源(task sources)

  1. DOM 操作任务源(The DOM manipulation task source):用于对 DOM 操作做出反应的功能,例如在将元素插入文档时以非阻塞方式发生的事情。
  2. 用户交互任务源(The user interaction task source):用于对用户交互做出反应的功能,例如键盘或鼠标输入。
  3. 网络任务源(The networking task source):用于响应网络活动而触发的功能。
  4. 导航和遍历任务源(The navigation and traversal task source):用于对导航和历史遍历中涉及的任务进行排队。

处理模型

只要事件循环存在,它就必须持续运行以下步骤:

  1. 让 oldestTask 和 taskStartTime 为 null。
  2. 如果事件循环有一个任务队列其中至少有一个可运行的任务,则:
    1. 由“实现”(浏览器)定义的规则,选择一个任务队列作为 taskQueue。(“微任务队列”并不是一个“任务队列”,所以不会被选择)——【“选择哪个任务队列”:由浏览器定义(即文档中的“implementation-defined”,客户端软件本身被称为一种“implementation”)决定,这就允许优先选择对性能敏感的任务】
    2. 将 taskStartTime 设置为“不安全的共享当前时间”。
    3. 将 oldestTask 设置为 taskQueue 中的第一个可运行任务,并将其从 taskQueue 中删除。——【任务队列是“set”,所以并非“出队”】
    4. 将事件循环的“正在运行的任务”设置为 oldestTask。
    5. 执行 oldestTask 的步骤(Steps)。
    6. 将事件循环的“正在运行的任务”设置回 null。
  3. 执行微任务检查点
    1. 如果事件循环的“正在执行微任务的检查点”为 true,则返回。——【检查重入】
    2. 将事件循环的“正在执行微任务的检查点”设置为 true。——【防止重入】
    3. 当事件循环的“微任务队列”不为空时:——【!!!不为空将一直重复执行!!!】
      1. 从事件循环的“微任务队列”中“出队”一个任务作为 oldestMicrotask。
      2. 将事件循环的“正在运行的任务”设置为 oldestMicrotask。
      3. 执行 oldestMicrotask。
        • 这可能涉及调用脚本的回调,并最终调用“clean up after running script”步骤,而该步骤的最后一步又将调用执行微任务检查点。这就是为什么设置“正在执行微任务的检查点”来防止“重入”。
      4. 将事件循环的“正在运行的任务”设置回null。
    4. 对于负责的事件循环为该事件循环的每个“环境设置对象”,通知该“环境设置对象”上 rejected 的 promise。
    5. 清理索引数据库事务。
    6. 执行 ClearKeptObjects()。
    7. 将事件循环的“正在执行微任务的检查点”设置为false。
  4. 将 hasARenderingOpportunity 设置为 false。
  5. 将现在时刻设置为“不安全的共享当前时间”。
  6. 如果 oldestTask 不为 null,则:
    1. ……(省略)
  7. 更新渲染:如果这是一个窗口事件循环,则:
    1. ……(省略)
  8. ……(省略)

总结

Event Loop 执行任务的顺序为:任务队列的“第一个可运行任务”(宏任务) -> 微任务队列的所有任务(微任务) -> 渲染


对于说法:“微任务 -> 渲染 -> 宏任务”,从代码运行结果来看似乎没问题,其实忽略了一点“script 脚本本身也是一个任务(宏任务)”。

补充内容

到底什么是“Microtasks”?

无论是《ECMAScript® 2023 Language Specification》、《HTML Standard》、MDN,都没有找到关于“宏任务”、“微任务”的定义性描述。

——《ECMAScript® 2023 Language Specification》:压根就没提到 Microtasks 这个字

——《HTML Standard》:提到了 Microtask queuing:
    A microtask is a colloquial way of referring to a task that was created via the queue a microtask algorithm.
    微任务是一种口语化的方式,指的是通过“排队微任务算法”创建的任务。 ——【但文档中,与“任务”相比似乎只有“任务源”(task source)不同】

—— MDN:只提到了“任务”(Tasks)与“微任务”(Microtasks)的区别


此外,在 Jake Archibald 的文章[3]中提到:
    ECMAScript has the concept of "jobs" which are similar to microtasks, but the relationship isn't explicit aside from vague mailing list discussions. However, the general consensus is that promises should be part of the microtask queue, and for good reason.
    ECMAScript有类似于 microtasks 的“jobs”概念,但除了模糊的邮件列表讨论之外,这种关系并不明确。然而,普遍的共识是,promises 应该是 microtask queue 的一部分,这是有充分理由的。
    ……
    As mentioned, in ECMAScript land, they call microtasks "jobs". In step 8.a of PerformPromiseThen, EnqueueJob is called to queue a microtask.
    如前所述,在 ECMAScript 领域,他们将微任务称为“jobs”。在 PerformPromiseThen 的步骤 8.a 中,调用 EnqueueJob 对 microtask 进行排队。


再此外,javascript.info 中有提到:
    Microtasks come solely from our code. They are usually created by promises……[4]
    微任务仅来自于我们的代码。它们通常是由 promise 创建的……
    
    Asynchronous tasks need proper management. For that, the ECMA standard specifies an internal queue PromiseJobs, more often referred to as the “microtask queue” (V8 term).[5]
    异步任务需要适当的管理。为此,ECMA 标准规定了一个内部队列 PromiseJobs,通常被称为“微任务队列(microtask queue)”(V8 术语)。

所以,Promise 异步任务(注意,仅仅是它的回调,而不是它本身)是 microtask 应该是“潜规则”了,此外,我们还可以使用“queueMicrotask()”方法来添加 microtask。

——【P.S. 对于 Node.js 并不清楚】

如何添加 microtask?

如何实现无延迟的 setTimeout?

参考

  1. 1.0 1.1 参考:《HTML Standard》:“Event loops”一节
  2. “代理”(Agent)包括一组:ECMAScript执行上下文、执行上下文堆栈、运行的执行上下文、代理记录和执行线程。除了执行线程之外,代理的组成部分只属于该代理。 ——《ECMAScript® 2023 Language Specification》

    从概念上讲,代理概念是一个独立于体系结构、理想化的“线程”,JavaScript代码在其中运行。 ——《HTML Standard》

    每个代理由一组执行上下文的集合、执行上下文栈、主线程、一组可能创建用于执行 worker 的额外的线程集合、一个任务队列以及一个微任务队列构成。除了主线程(某些浏览器在多个代理之间共享的主线程)之外,其他组成部分对该代理都是唯一的。 ——MDN:《深入:微任务与 Javascript 运行时环境》

    • 一个宿主(浏览器、Node.js)可以有多个 Agent,当两个 Agent 同处一个“Agent Clusters”(代理集群)时,它们可以共享内存。
    • Agent 之间相互隔离,可以通过发送消息进行通信。
    • 一个 Agent 对应一条“执行线程”。
    • 每个 ECMAScript 程序都必须在一个名为 Agent 的区域内执行。

    参考:

    1. 《ECMAScript® 2023 Language Specification》:“Agents”一节
    2. 《HTML Standard》:“Agents and agent clusters”一节
    3. MDN:《深入:微任务与 Javascript 运行时环境》
    4. 【人人都能读标准】8.可视化JavaScript的运行环境:agents、执行上下文、Realm
  3. 参考:《Tasks, microtasks, queues and schedules》——Jake Archibald
  4. 参考:javascript.info:《Event loop: microtasks and macrotasks》
  5. 参考:javascript.info:《Microtasks》