EventLoop也被称为事件循环,指的是计算机系统的一种运行机制,在 JavaScript 运行时中是一个重要机制,它解决了JavaScript单线程的种种问题,会根据不同的任务源通过算法运行不同的优先级,从而达到对不同时机的任务的执行。
它的存在也可以说来是为了负责协调和管理 JavaScript 程序中的各种任务的执行,通过将任务分配到不同的对列中,并且按照一定的规则来管理执行顺序。我们所熟悉是事件交互、定时器,Promise,背后都有EventLoop在运作。
定义
事件循环是定义在HTML 的标准中的。接下来看下事件循环的基本定义。
To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. Each agent has an associated event loop, which is unique to that agent.
通过上述的阐述,我们知道了事件循环的存在为了协调 事件、用户交互、脚本、渲染、网络等事件的一种机制,并且每个用户代理都关联一个事件循环,并且每个事件代理都是唯一的。
与其说 JavaScript提过了事件循环,不如说是嵌入JavaScript的user agent(浏览器)需要通过事件循环的机制来达到和多种事件源的交互。
任务
前面提到事件循环是根据不同的任务源通过算法运行不同的优先级,这里就牵扯到任务概念了:一个任务就是由诸如一段程序、事件回调或一个 interval/timeout 被触发之类的标准机制而被调度的任意 JavaScript 代码。这些将都在任务队列(task queue)上被调度,而一个事件循环,有存在一个至多个任务对列,任务队列是一系列排好序的任务组成。下面是列举的出来的任务。
- Events(事件): 在一个特定的 EventTarget 对象上分派一个事件对象通常由一个专门的任务来完成,如click、input....;
- Parsing(解析): HTML解析器对一个或多个字节进行标记,然后处理结果标记,这通常是一项任务;
- Callbacks(回调): 调用回调通常是由一个专门的任务来完成的;
- Using a resource(加载资源): 当算法获取资源时,如果获取是以非阻塞的方式进行的,那么一旦部分或全部资源可用,则执行任务对资源进行处理;
- Reacting to DOM manipulation(响应操作DOM): DOM操作而触发的任务,例如当该元素被插入到文档中时。
任务也是一个结构体:
- Steps:任务要完成工作的一系列步骤
- Source:每个任务都有任务源,用于对相关任务进行分组和序列化
- Document: 与任务相关联的文档,对于不在 window 事件循环中的任务,则为空。
- 脚本对象集: 环境设置对象,用于在任务期间跟踪脚本评估
标准中的话术隐晦难懂,但也不难看出在标准中使用任务源来分离逻辑上不同类型的任务,比如可以为鼠标、键盘事件提供一个 task 队列,其他事件又是一个单独的队列。这样可以为鼠标、键盘事件分配更多的时间,保证交互的流畅。
而用户代理(浏览器)也可能希望区分这些任务。使用任务队列来合并给定事件循环中的任务源。
任务的分类
任务可分为 task(macrotask) 和 microtask
Macrotask(宏任务)
1. 主要是浏览器协调各类事件的队列,虽然叫做队列,本质上是集合(set),在HTML标准中也称为之task queue;
2. 传统的队列都是先进先出的(FIFO)的,而事件循环处理模型的步骤是从选择的的队列中抓取第一个可运行的任务,而不是先进的任务,就算是拍到最前面的任务,在没有满足条件也不会执行;
3. 不同的Task事件源的队列可以有不同的优先级(例如网络事件和用户交互之间,浏览器可以优先处理鼠标行为,从而让用户感觉更加流畅)。
4. Generic task sources 定义的 macrotask 任务源有如下
4.1 DOM操作任务源:此任务被用来相应DOM操作(页面渲染),例如一个元素以飞阻塞的 方式插入文档;
4.2 用户交互人无远:此任务源用来的对任务交互做出反应,例如键盘或者鼠标输入;
4.3 history traversal 任务源:此任务源用户当调用history时,讲任务出入task队列;
4.4 定时器任务源:可以使用setTimeout或者setInterval来添加任务。
Microtask (微任务)
1. 微任务和宏任务之间的差异看起来不大,很相似,都由位于某个队列的 JavaScript 代码组件并在合适的时候运行;
3. 当只有Javascript 调用栈为空,而控制权还没有交还给user agent 之前,该任务才会执行,也符合了上一条的描述。
4. 每个事件循环都有一个 microtask 队列,microtask 最初在 microtask 队列中排列而不是在 task 队列。
5. 在规范说明中有两类 microtask(规范没有那些是solitary callback microtask,那些是compound microtask)
5.1 单独回调微任务(solitary callback microtask)
5.2 复合微任务(compound microtask)
6. 当算法要求 microtask 排列时,它必须被添加到相关事件循环的 microtask 队列;这个 microtask 的任务源是微任务任务源(microtask task source)
7. microtask 的初始执行过程中,microtask 也可能被移动到常规的任务队列,将转动事件循环。这种情况下,微任务任务源将是使用的任务源。通常,microtask 的任务源是不相关的。
8. 在 HTML 标准中,并没有明确规定这个队列的事件源,通常认为有以下几种:
8.1 Promise;
8.2 Object.observe(已弃用);
8.3 MutationObserve;
8.4 queueMicrotask。
Macrotask VS Microtask 的区别
1. task 至少一个,都是由制定的任务源去 提供的;
2. 事件循环中只能由一个 Microtask队列。
事件循环处理模型(Process Model)
event loop 的处理过程
-
在 tasks 队列中选择最老的一个 task,如果没有可选的任务,则跳到下边的 microtasks 步骤
-
将事件循环当前执行任务设置为 oldestTask
-
设置 taskStartTime 为当前的current high resolution time
-
执行 oldestTask
-
将事件循环当前执行任务设置为 null
-
Microtasks: 执行 Microtasks 检查点
-
hasARenderingOpportunity 标志置为 false
-
设置当前的current-high-resolution-time
-
设置最新的 top-level browsing contexts并 Report long tasks
-
更新渲染(如果是一个浏览上下文的事件循环)
-
如果是一个 worker 的事件循环,但是事件循环的任务队列中没有任务并且 WorkerGlobalScope 对象的关闭标识(closing flag)是 true 的话,销毁这个事件循环,中断以上步骤,恢复运行在 Web worker 中描述的 worker 步骤
-
返回第一步
概括说来就是 event loop 会不断循环的去取 tasks 队列的中最老的一个任务推入栈中执行,并在当次循环里依次执行并清空 microtask 队列里的任务。执行完 microtask 队列里的任务,有可能会渲染更新。
microtask checkpoint
当执行 microtask 的检查点时,如果检查点标识为 false, 必须运行以下步骤:
1. 将检查点flag 设置为true;
2. 当事件循环的microtask 不为空时;
2.1 将 oldestMicrotask 设置为事件循环的 microtask 队列中最老的 microtask;
2.2 将事件循环当前执行任务设置为 oldestMicrotask
2.3 执行 oldestMicrotask;
2.4 将事件循环当前执行任务设置为 null;
2.5 将 oldestMicrotask 从 microtask 队列中移除;
3. 每一个environmenht settings object 它们的 responsible event loop就是当前的event loop,会给 environment settings object发一个 rejected promises 的通知;
4. 清除索引数据库事务;
5. 将检查点标识设置为 false;
6. 回到步骤2,继续执行剩下的微任务;
当一个组合微任务运行时,UA 必须运行一系列步骤来执行组合微任务的子任务,步骤如下:
1 让父任务是事件循环当前执行任务;
2 让子任务变为由执行一系列给定步骤的新任务。这个 microtask 的任务源是 microtask task source。这是一个组合微任务的子任务;
3 将事件循环当前执行任务设置为子任务;
4 执行子任务;
5 将事件循环当前执行任务设置为父任务;
当并行运行的算法要等待稳定状态时,UA 必须运行以下步骤对 microtask 排列,然后停止执行(如下列步骤所述,当 microtask 执行时算法将恢复运行):
1. 运行算法的同步部分;
2. 如果可以,按照算法步骤中的描述,恢复并行算法的运行。
当一个算法要推动事件循环直到符合条件目标时,UA 必须执行以下步骤:
1. 将事件循环当前任务设置为该任务;
2. 将任务的任务源设置为该任务任务源
3. 将 JS 执行上下文栈拷贝给旧栈(old stack);
4. 清空 JS 执行上下文栈;
5. 运行 microtask 检查点;
6. 停止任务,允许任何调用它的算法恢复,但是并行继续这些步骤;
7. 等待符合的条件目标出现;
8. 列一个任务继续这些步骤,使用任务源的任务源。在新任务运行后继续执行这些步骤。;
9. 用旧栈替换 JS 执行上下文栈;
10. 返回调用者。
概括的来说就是要执行微任务就先执行微任务检查点,什么时候执行检查点则是上下文执行栈为空的时候,也就是在 task 执行之后渲染之前。执行的时候也是持续运行直到微任务任务队列中为空停止(如图所示)。
三种事件循环
在浏览器中不是只有一种类型的 eventloop,而每种类型的 eventloop 的处理模型也不相同,大致分为下面三种类型
浏览器上下文
每个
标签:队列,microtask,EventLoop,任务,循环,事件,执行 From: https://www.cnblogs.com/goather/p/17206638.html