查看“Event Loop:事件循环”的源代码
←
Event Loop:事件循环
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
[[category:JavaScript]] == 关于 == 在 MDN 上看到 JavaScript 的[https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop 《并发模型与事件循环》]一节,但由于该页面内容过少,实在不变学习。 而网络上搜到的内容大同小异,互相又有不小出入,难以拿捏。 众所周知,JavaScript 是没有所谓源代码的“源代码”,所以这部分内容只能在“规范”文件<ref name="HTML-Standard">参见:[https://html.spec.whatwg.org/multipage/webappapis.html#event-loops 《HTML Standard》:“Event loops”一节]</ref>了。 * * 以下内容参照《HTML Standard》、MDN、与网络内容整理。 == 前置知识 == 见:<big>'''[[浏览器基础:进程与线程]]'''</big> “一个 Tab 页面对应一个'''渲染进程'''”,而“渲染进程中只能存在一个 '''JS 引擎线程'''”(即,'''主线程'''),即:页面中的所有 JS 都由同一单线程运行。 ??????由于“渲染进程”负责了页面几乎所有的任务(事件、交互、脚本、渲染、网络),所以如何协调各个任务的执行是关键问题。 [[File:浏览器的进程与线程.png|400px]] === JavaScript的“任务” === JavaScript中,“任务”被分为两种:“同步任务”,“异步任务”。“异步任务”(Task):又分为“宏任务”(MacroTask)与“微任务”(MicroTask)。 # 同步任务:在“主线程”上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。 # 异步任务:不直接进入“主线程”,而进入“任务队列”(task queue)的任务。 ## '''宏任务(macrotask)''': ## '''微任务(microtask)''': === JavaScript的“运行时” === 与 Java 的运行时概念类似,JavaScript 也有其运行时。 == Event Loop == 要协调事件(event),用户交互(user interaction),脚本(script),渲染(rendering),网络(networking)等,用户代理必须使用本节中描述的事件循环。 每个“代理”(Agent<ref> <blockquote> “代理”(Agent)包括一组:ECMAScript执行上下文、执行上下文堆栈、运行的执行上下文、代理记录和执行线程。除了执行线程之外,代理的组成部分只属于该代理。 </blockquote> 从概念上讲,代理概念是一个独立于体系结构、理想化的“线程”,JavaScript代码在其中运行。 * 一个宿主(浏览器、Node.js)可以有多个 Agent,当两个 Agent 同处一个“Agent Clusters”(代理集群)时,它们可以共享内存。 * Agent 之间相互隔离,可以通过发送消息进行通信。 * 一个 Agent 对应一条“执行线程”。 * 每个 ECMAScript 程序都必须在一个名为 Agent 的区域内执行。 参考: # [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://github.com/Lawliet01/everyone-can-read-spec/blob/main/8.execution-environment.md 【人人都能读标准】8.可视化JavaScript的运行环境:agents、执行上下文、Realm] </ref>)有一个关联的“事件循环”,且该循环对该代理是'''唯一'''的。 # Similar-origin window agent 的事件循环称为“窗口事件循环”(window event loop)。 # Dedicated worker agent、Shared worker agent 或 Service worker agent 的事件循环称为“工作事件循环”(worker event loop)。 # Worklet agent 的事件循环称为“工件事件循环”(worklet event loop)。 * 事件循环不一定对应于实现线程。(例如,可以在单个线程中协同调度多个窗口事件循环) * 一个事件循环具有'''一个或多个“任务队列”(task queues)'''。 ** <q>'''任务队列是“集合”(set),而不是“队列”(queue)''',因为事件循环处理模型从所选队列中获取第一个可运行的任务,而不是将第一个任务出列。</q> ** <q>'''微任务队列(microtask queue)不是任务队列(task queues)'''。</q> * 每个事件循环都有一个当前“正在运行的任务”(currently running task),要么是一个任务,要么是 null。初始为空。 ** 它用于处理可重入性。 * 每个事件循环都有'''一个“微任务队列”(microtask queue)''',该队列是一个初始为空的微任务队列。 * 每个事件循环都有一个“正在执行微任务的检查点”(performing a microtask checkpoint)的布尔值。该值最初为 false。 ** 它用于防止重入调用。 * 每个窗口事件循环都有一个 DOMHighResTimeStamp 表示“上次渲染机会时间”(last render opportunity time),最初设置为零。 * 每个窗口事件循环都有一个 DOMHighResTimeStamp 表示“上一个空闲周期的开始时间”(last idle period start time),最初设置为零。 === 任务(Tasks) === “任务”(Tasks)包括以下几种: # 事件(Events):在特定 EventTarget 对象上调度 Event 对象,通常由专用的任务完成。 # 解析(Parsing):HTML 解析器标记一个或多个字节,然后处理任何生成的标记,通常是一项任务。 # 回调(Callbacks):调用回调通常由一个专用的任务完成。 # 使用资源(Using a resource):当算法获取资源时,如果获取是以非阻塞的方式发生的,那么一旦部分或全部资源可用,就由任务执行对资源的处理。 # 对 DOM 操作做出反应(Reacting to DOM manipulation):一些元素具有响应DOM操作而触发的任务,例如:当该元素被插入到文档中时。 从形式上讲,“任务”(Tasks)具有以下结构: # 步骤(steps):指定任务要完成的工作的一系列步骤。 # 来源(source):用于对相关任务进行分组和序列化。 # 文档(document)与任务关联的文档,对于不在“窗口事件循环”中的任务则为 null。 # 脚本评估环境设置对象集(A script evaluation environment settings object set):用于跟踪任务期间的脚本评估。 根据其源字段(source),每个任务都被定义为来自特定的“任务源”(task source)。对于每个事件循环,每个“任务源”都必须与特定的“任务队列”相关联。 === '''处理模型''' === 只要事件循环存在,它就必须持续运行以下步骤: # 让 oldestTask 和 taskStartTime 为 null。 # 如果事件循环有一个'''任务队列'''其中至少有一个可运行的任务,则: ## 由实现(浏览器)定义的规则,选择一个任务队列作为 taskQueue。(“微任务队列”并不是一个“任务队列”,所以不会被选择) ## 将 taskStartTime 设置为“不安全的共享当前时间”。 ## 将 oldestTask 设置为 taskQueue 中的'''第一个可运行任务''',并将其从 taskQueue 中删除。——【任务队列是“set”,所以并非“出队”】 ## 将事件循环的“正在运行的任务”设置为 oldestTask。 ## 执行 oldestTask 的步骤(Steps)。 ## 将事件循环的“正在运行的任务”设置回 null。 # '''执行微任务检查点''': ## 如果事件循环的“正在执行微任务的检查点”为 true,则返回。——【检查重入】 ## 将事件循环的“正在执行微任务的检查点”设置为 true。——【防止重入】 ## 当事件循环的“微任务队列”不为空时:——【!!!'''不为空将一直重复执行'''!!!】 ### 从事件循环的“微任务队列”中“出队”一个任务作为 oldestMicrotask。 ### 将事件循环的“正在运行的任务”设置为 oldestMicrotask。 ### 执行 oldestMicrotask。 ###* 这可能涉及调用脚本的回调,并最终调用“clean up after running script”步骤,而该步骤的最后一步又将调用'''执行微任务检查点'''。这就是为什么设置“正在执行微任务的检查点”来防止“'''重入'''”。 ### 将事件循环的“正在运行的任务”设置回null。 ## 对于负责的事件循环为该事件循环的每个“环境设置对象”,通知该“环境设置对象”上 rejected 的 promise。 ## 清理索引数据库事务。 ## 执行 ClearKeptObjects()。 ## 将事件循环的“正在执行微任务的检查点”设置为false。 # 将 hasARenderingOpportunity 设置为 false。 # 将现在时刻设置为“不安全的共享当前时间”。 # 如果 oldestTask 不为 null,则: ## ……(省略) # '''更新渲染''':如果这是一个窗口事件循环,则: ## ……(省略) # ……(省略) === 总结 === 当宏任务和微任务都处于 任务队列(Task Queue) 中时,微任务的优先级大于宏任务,即先将微任务执行完,再执行宏任务; 这是理解上优点小偏差 == 补充内容 == === 如何使用“微任务”(microtask)? === === 如何实现无延迟的 setTimeout? === == 参考 == <references/>
返回至“
Event Loop:事件循环
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
大陆简体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
笔记
服务器
数据库
后端
前端
工具
《To do list》
日常
阅读
电影
摄影
其他
Software
Windows
WIKIOE
所有分类
所有页面
侧边栏
站点日志
工具
链入页面
相关更改
特殊页面
页面信息