Event Loop:事件循环的神话与现实

在网络上有大量关于事件循环及其工作原理的介绍,新的文章也不断涌现。不幸的是,但是这些材料中提供的信息并非都经过验证或可靠的。最终,这个概念本身已被一些神话和猜测所围绕。有时,即使是经验丰富的开发人员也需要付出大量的注意力和经验,才能辨别真相与虚构之间的区别。

事件循环Event LoopJavaScript 中是否存在?

事件循环Event Loop确实存在。但是,它在ECMA-262规范中找不到。事件循环不是 JavaScript(ECMAScript)语言的一部分,因此,不受其规范的管辖。该术语存在于 HOST执行器领域,在其中的JavaScript引擎的具体实现可以自行决定是否利用事件循环作为执行环境的API

事件循环的官方信息来源是什么?

正如我们之前提到的,ECMA-262规范中没有提到事件循环一词,因为它超出了语言的范围,但属于负责执行 JavaScript代码的 HOST执行器的领域。因此,关于事件循环的信息应该从管理或记录这个实现环境的来源中寻找。今天有许多这样的环境,可以大致分为基于浏览器和非基于浏览器的环境。

浏览器的环境

这类环境的机制由 WHATWG 组织通过 HTML 规范进行管理。

具体来说,在规范的第 8.1.7节“事件循环”中涉及了事件循环。我们稍后将讨论 Web API中的事件循环的算法和标准。现在值得一提的是,浏览器通常依赖于执行它们的操作系统的API;例如,macOS中的 Chromium依赖于 NSRunLoop,在 Linux 中,它依赖于 glib

这里的一个例外是 Electron,由于其所谓的跨平台性质,遇到了在不同操作系统上实现事件循环的挑战,因此过渡到使用libuv库,类似于Node.js(稍后会详细介绍)。

非浏览器的环境

在非浏览器的环境中,正如名称所示,HTML标准没有被实现。由于除ECMA-262 外,这类环境应该如何运行没有国际标准和规范,因此它们自己的文档是唯一的官方信息来源。

迄今为止,最常见的非浏览器环境是 Node.js

Node.js 文档中,有一个关于libuv 事件循环的章节,描述了Node API中唯一可用的函数napi_get_uv_event_loop,旨在获取对当前事件循环的引用。

不幸的是,这个文档没有提供事件循环的其他描述。但是,很明显,为了确保其运行,Node.js使用了 libuv 库,这个库专门为 Node.js开发,为此环境提供了事件循环的实现。现在,该库也被一些其他项目使用。该库的内部文档包含 uv_loop_t API部分,提供了事件循环的正式API规范。此外,文档还包含了事件循环的示意图以及在该库中使用事件循环的指南。


其他非浏览器环境,如 DenoBun,也依赖于libuv库来处理事件循环。移动环境,包括React NativeNativeScriptApache Cordova,也是非基于浏览器的。它们依赖于执行它们的操作系统的API。例如,对于Android,这是 Android.os.Looper,而对于iOS,这是RunLoop

ECMA-262 规范如何定义事件循环

如果没有了事件循环机制,JavaScript代码的执行将变得极其困难。ECMA-262规范怎么会忽视这么重要的方面呢?

尽管 ECMA-262规范中没有包括事件循环一词,但这并不意味着它没有以任何方式规范代码执行过程。然而,这种规范不集中在一个具体的概念下。总的来说,整个“可执行代码和执行上下文”(Executable Code and Execution Contexts)的第 9 节都致力于 JavaScript执行过程。在这个部分中,第 9.7 条“Agents”引入了“Agent”一词,并提供了 Agent Record的结构,其中包括负责阻塞该代理的字段。代理的实现仍然由 HOST执行器负责,但有一些限制。具体而言,第 9.9 节“Forward Progress”概述了代理实现的基本要求:

  • 当其正在运行的执行上下文同步且无限期地等待外部事件时,代理会变为阻塞状态
  • 只有其 Agent Record[[CanBlock]]字段为 true的代理才能以这种方式变为阻塞状态
  • 一个未阻塞的代理是一个没有被阻塞的代理
  • 每个具有专用执行线程的未阻塞代理最终都会向前进展
  • 在共享执行线程的一组代理中,一个代理最终会向前进展
  • 一个代理不会使另一个代理变为阻塞状态,除非通过明确提供阻塞的显式 API

这些限制,加上第 29 节“Memory Model”中的保证,足以使所有 SEQ-CST记录最终对所有代理可观察到。

HTML 规范中对事件循环的描述可以被视为参考吗?

由于官方的事件循环规范仅存在于HTML标准中,因此有相当多的非浏览器变体。这些都是由开发人员自行决定开发的,每种都有自己的特点。对于每种变体都需要一个单独的文章(一些已经在线上可用)。此外,许多实现在某种程度上或多或少地依赖于HTML规范,以防止重复造轮子,这是合理的。

是否将HTML规范视为事件循环部分的参考是一个有争议的问题。这个问题没有明确的答案,但考虑到以上内容,从现在开始,我们将运用这个规范来进一步考虑这个问题。

事件循环只针对同步和异步的 JavaScript 操作

正如我们之前提到的,事件循环不属于 JavaScript语言领域。对于JavaScript来说,它是一种外部机制(如果你愿意,可以称之为“服务”),它允许你组织你的工作。与此同时,事件循环本身并不仅限于执行JS代码。事件循环负责许多进程,如输入/输出操作(鼠标和键盘事件,文件读写等)、事件协调、渲染、网络操作等等。

事件循环是否线程安全?

这个问题非常有趣。早些时候,我们讨论了 ECMA-262规范中的“9.9 Forward Progress”,该节对代理的实现设置了一定的限制。该部分并没有明确指出线程安全性。相反,它指出,如果同一个线程中有多个代理,只有一个代理应该进展。这个模型清楚地表明,不需要线程安全性,因为一次只有一个代理可以工作。
在大多数情况下,是的。例如,Node.js 中使用的libuv库明确说明它们的实现不是线程安全的,它们的事件循环应该在单线程中使用,或者以多线程模式独立组织工作。
然而,对于浏览器实现来说,情况并不那么简单。
首先,值得澄清的是,在 HTML规范的第8.1.2 节“代理和代理集群”中,规范标识了几种类型的代理:

  • 类似源窗口代理 : 包含各种Window对象,这些对象可以直接或通过使用 document.domain 相互访问
  • 专用工作线程代理 : 包含单个 DedicatedWorkerGlobalScope(具有与之关联的隐式 MessagePort 的范围)
  • 共享工作线程代理 : 包含单个SharedWorkerGlobalScope(具有构造函数来源、构造函数 URL 和凭证)
  • 服务工作线程代理 : 包含单个SharedWorkerGlobalScope(具有关联的服务工作线程)
  • Worklet 代理 : 包含单个WorkletGlobalScope(由于 worklets可以导入 ES 模块,所以此范围具有关联的模块映射)

根据代理的类型,规范标识了三种类型的事件循环:

  • 窗口事件循环 - 用于类似源窗口代理
  • 工作线程事件循环 : 用于专用工作线程代理、共享工作线程代理和服务工作线程代理
    -worklet事件循环 : 用于Worklet代理

在这些中,工作线程事件循环和worklet事件循环的代理标志[[CanBlock]]设置为true,迫使它们遵循“9.9 Forward Progress”的限制。因此,这样的事件循环将在它们自己的专用线程中工作。
另一方面,可以在同一个线程中同时使用多个窗口事件循环(例如,如果浏览器开发人员希望,几个浏览器选项卡可以共享一个线程)。

事件循环由宏任务和微任务组成的吗?

不完全正确。规范中实际上不存在“宏任务”这个术语。实际上,事件循环由任务队列和微任务队列组成,它们的机制基本上是不同的。
值得注意的是,与其名称相反,任务队列在技术上不是队列;它实际上是一个集合。相比之下,微任务队列确实是一个队列。这导致了一个重要的区别:在下一次迭代开始时,任务队列可能包含不同状态的许多任务。传统上,队列算法假定第一个元素从队列中移除(出队)。然而,对于任务队列,第一个元素在特定时刻不一定是可运行的任务。与标准队列算法不同,该过程不是简单的出队操作,而是要定位可运行任务状态的第一个任务,并从集合中提取它。另一方面,微任务被放入队列,并按照它们被添加的顺序进行移除。对这一过程的更深入的了解将在下面详细介绍。

任务队列中包含哪些内容?

在这个问题上,通常会出现认知上的不一致。一方面,任务队列用于延迟任务处理,即异步执行。但是同步代码会发生什么?为了解决这个问题,值得在 JavaScript之外稍作深入(因为我们已经知道事件循环是在JavaScript之外运行的),并意识到对于浏览器来说,JS 代码本身只是它处理的众多实体之一。通过解析脚本文件或 <script>标签,浏览器会收到一个标记化的结果。这个标记化结果的完成本身就成为一个独立的任务,在事件循环中生成一个全局任务类型的任务。因此,实际上,同步代码从执行的一开始就已经以任务的形式存在于事件循环中。
此外,随着脚本的执行,会生成新的任务,并放置在同一个事件循环中。
还有哪些内容会进入任务队列?

  • Events : 将事件对象分派给特定的 EventTarget 对象通常由专用任务执行,但并非总是如此。许多事件会在其他任务中分派。例如,MouseEventKeyboardEvent事件可以合并为一个任务,其源与用户交互任务源相关联
  • Parsing : HTML解析器标记化一个或多个字节,然后处理任何生成的标记,通常是一个任务
  • Callbacks : 通常,它们会落入任务队列,setTimeout(() => {})的经典示例也是如此,在这种情况下,一个callback被传递给 setTimeout,它将成为任务队列中的一个单独任务
  • 使用资源 - 在非阻塞资源获取的情况下(例如图像),会在任务队列中设置一个单独的任务
  • 响应 DOM 操作 - 一些元素在响应 DOM操作时会在任务队列中生成一个任务。举例来说,向 DOM 插入一个新元素会触发重新渲染父元素的任务

微任务队列有什么目的?哪些任务是微任务?

在任务队列的情况下,定义和添加任务到队列的责任在于代理。微任务队列是运行时通过 Web API 提供给任务的一个选项,使任务能够满足其自身的异步需求。技术上讲,无论是执行 JavaScript 代码、标记化 HTML文本、处理 I/O 事件还是其他操作,每个任务都可以使用微任务队列来实现其目标。
具体来看 JavaScript,该语言假设存在其自身的异步操作,这些操作不受 HTML 规范的约束。这些操作最好通过 Promise 进行说明。更具体地说,规范的第 27.2.1.3.2 节“Promise 解析函数”描述了解析 promise的过程。第 15 步涉及执行 HostEnqueuePromiseJob,其实现位于 HOST 执行器中,但遵循某些要求,例如按照调度它们的 HostEnqueuePromiseJobs 的顺序运行作业。
如前所述,HostEnqueuePromiseJobs 机制的实现完全由 HOST 执行器负责。然而,在这种情况下使用微任务队列似乎是非常合理的。为了进一步澄清,让我们参考其中一个最广泛使用的JavaScript引擎 - V8 的源代码
/src/objects/objects.cc#4839

/ https://tc39.es/ecma262/#sec-promise-resolve-functions
// static
MaybeHandle<Object> JSPromise::Resolve(Handle<JSPromise> promise,
                                       Handle<Object> resolution) {
  //...
  //...
  //...

  // 13. Let job be NewPromiseResolveThenableJob(promise, resolution,
  //                                             thenAction).
  Handle<NativeContext> then_context;
  if (!JSReceiver::GetContextForMicrotask(Handle<JSReceiver>::cast(then_action))
           .ToHandle(&then_context)) {
    then_context = isolate->native_context();
  }

  Handle<PromiseResolveThenableJobTask> task =
      isolate->factory()->NewPromiseResolveThenableJobTask(
          promise, Handle<JSReceiver>::cast(resolution),
          Handle<JSReceiver>::cast(then_action), then_context);
  if (isolate->debug()->is_active() && IsJSPromise(*resolution)) {
    // Mark the dependency of the new {promise} on the {resolution}.
    Object::SetProperty(isolate, resolution,
                        isolate->factory()->promise_handled_by_symbol(),
                        promise)
        .Check();
  }
  MicrotaskQueue* microtask_queue = then_context->microtask_queue();
  if (microtask_queue) microtask_queue->EnqueueMicrotask(*task);
  
  // 15. Return undefined.
  return isolate->factory()->undefined_value();
}

在 V8 的实现中,我们可以观察到,为了执行HostEnqueuePromiseJob,引擎将相应的微任务放入了微任务队列,从而证实了我们的假设。

事件循环如何运作的?

如上所述,有许多实现算法的变体。基于浏览器的环境遵循 HTML 规范,并且通常依赖于执行环境的操作系统 API 来实现机制。在这种情况下,值得在每个具体浏览器引擎的源代码中,以及相应的库和操作系统 API 中寻找实现示例。在使用第三方库(例如 libuv)的平台上,可以在该库本身中找到实现示例(libuv 是开源的)。然而,应该理解每个实现都是独立的,可能与其他实现有很大的差异。

为了举例说明,并且不依赖于任何特定的实现,让我们尝试描绘我们伪版本的机制。鉴于我们在 JavaScript 的背景下进行讨论,为了易于理解和理解,我们将在 TypeScript 中实现它。

以下清单显示了事件循环操作所必需的接口。这些接口仅用于演示目的,符合 HTML 规范,并且以任何真实 HOST 执行器的内部结构。事件循环算法本身将在下面给出。

如前所述,有几种实现此算法的变体。基于浏览器的环境遵循 HTML 规范,并且通常依赖于操作系统 API 进行实际实现。在这方面,建议在每个具体浏览器引擎的源代码中以及相应的库和操作系统 API 中寻找实现示例。在使用第三方库的平台上,如 libuv,有利于在库本身内检查实现示例(libuv是开源的)。然而,重要的是要注意,每个实现都是独立的,并且可能与其他实现有很大的不同。

为了举例说明,并试图不依赖于任何特定的实现,让我们考虑机制的伪版本。考虑到我们在JavaScript的背景下讨论,为了理解和清晰起见,我们将在 TypeScript 中实现此功能。

以下清单显示了事件循环操作所必需的接口。需要注意的是,这些接口纯粹作为演示提供,符合 HTML 规范,并且不反映任何真实 HOST 执行器的内部结构。

/**
* A browsing context is a programmatic representation of a series of documents, multiple of which can live within a
* single navigable. Each browsing context has a corresponding WindowProxy object, as well as the following:
*
* - An `opener browsing context`, a browsing context or null, initially null.
* - An `opener origin` at creation, an origin or null, initially null.
* - An `is popup` boolean, initially false.
* - An `is auxiliary` boolean, initially false.
* - An `initial UR`L, a URL or null, initially null.
* - A `virtual browsing context group ID` integer, initially 0. This is used by cross-origin opener policy reporting,
*   to keep track of the browsing context group switches that would have happened if the report-only policy had been
*   enforced.
*
* A browsing context's active window is its WindowProxy object's [[Window]] internal slot value. A browsing context's
* active document is its active window's associated Document.
*
* A browsing context's top-level traversable is its active document's node navigable's top-level traversable.
*
* A browsing context whose is auxiliary is true is known as an auxiliary browsing context. Auxiliary browsing contexts
* are always top-level browsing contexts.
*
* Note: For a demonstration purposes and for simplicity the BrowserContext is reflecting the Window interface which is
*       not fully correct, as the might be different implementations of the BrowserContext.
*/
interface BrowsingContext extends  Window {}
  
/**
 * A navigation request is a request whose destination is "document", "embed", "frame", "iframe", or "object"  *
 * Note: For a demonstration purposes and for simplicity the NavigationRequest is reflecting the Window interface
 *       which is not correct as the NavigationRequest is a different structure mostly use for
 *       `Handle Fetch` (https://w3c.github.io/ServiceWorker/#handle-fetch)
 */
interface NavigationRequest extends Window {}
  
interface Environment {
  id: string;
  creationURL: URL;
  topLevelCreationURL: URL;
  topLevelOrigin: string | null;
  targetBrowsingContext: BrowsingContext | NavigationRequest | null;
  activeServiceWorker: ServiceWorker | null;
  executionReady: boolean;
}

interface EnvironmentSettingsObjects extends Environment {
  realmExecutionContext: ExecutionContext;
  moduleMap: ModuleMap;
  apiBaseURL: URL;
  origin: string;
  policyContainer: PolicyContainer;
  crossOriginIsolatedCapability: boolean;
  timeOrigin: number;
}

interface Task {
  // A series of steps specifying the work to be done by the task.
  // will be defined in a certain Task implementation
  steps: Steps;
  
  // One of the task sources, used to group and serialize related tasks.
  //
  // Per its source field, each task is defined as coming from a specific task source. For each event loop, every
  // task source must be associated with a specific task queue.
  //
  // Essentially, task sources are used within standards to separate logically-different types of tasks,
  // which a user agent might wish to distinguish between. Task queues are used by user agents to coalesce task sources
  // within a given event loop.
  source: unknown;
  
  // A Document associated with the task, or null for tasks that are not in a window event loop.
  // A task is runnable if its document is either null or fully active.
  document: Document | null;
  
  // A set of environment settings objects used for tracking script evaluation during the task.
  environmentSettingsObject: Set<EnvironmentSettingsObjects>;
}

interface GlobalTask extends Task {
  steps: Steps; // redefine/implement steps for this particular task type
}

interface EventLoop {
  taskQueue: Set<Task>;
  
  // Each event loop has a microtask queue, which is a queue of microtasks, initially empty. A microtask is a colloquial
  // way of referring to a task that was created via the queue a microtask algorithm.
  //
  // For microtaskQueue is used just to illustrate that the specification supposes it to be a logical queue, rather
  // than a set of tasks. From technical perspective, a real implementation might use a `Set` like for taskQueue, or
  // any other structure at the discretion of the agent's developer.
  microtaskQueue: Array<Task>;
  
  // Each event loop has a currently running task, which is either a task or null. Initially, this is null. It is used
  // to handle reentrancy.
  currentRunningTask: Task | null;
  
  // Each event loop has a performing a microtask checkpoint boolean, which is initially false. It is used to prevent
  // reentrant invocation of the perform a microtask checkpoint algorithm.
  performingAMicrotaskCheckpoint: boolean;
}

interface WindowEventLoop extends EventLoop {
  // Each window event loop has a DOMHighResTimeStamp last render opportunity time, initially set to zero.
  lastRenderOpportunityTime: number;
  
  // Each window event loop has a DOMHighResTimeStamp last idle period start time, initially set to zero.
  lastIdlePeriodStartTime: number;
}

/** Just for demonstration purposes. Such a helper not necessarily should be presented in the real implementation */
function isWindowEventLoop(eventLoop: EventLoop): eventLoop is WindowEventLoop {
  return 'lastRenderOpportunityTime' in eventLoop && 'lastIdlePeriodStartTime' in eventLoop;
}

我们已经描述了必要的接口,至少是我们需要理解下面算法的部分。现在,让我们来看一下事件循环算法本身。需要澄清的是,该算法是对 8.1.7.3 处理模型规范的说明,不以任何方式反映 HOST 执行器上的实际实现。

/**
 * Processing the event loop according to the `8.1.7.3 Processing model`
 * https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model
 */
function runEventLoop(eventLoop: EventLoop) {
  // 1. Let oldestTask and taskStartTime be null.
  let oldestTask: Task | null = null;
  let taskStartTime: number | null = null;
   
  while (true) {
    // 2. check if the taskQueue has a runnable task and if there is one
    //   2.1. Let taskQueue be one such task queue, chosen in an implementation-defined manner.
    //   2.2. ... will be done below
    //   2.3. Set oldestTask to the first runnable task in taskQueue, and remove it from taskQueue.
    oldestTask = getFirstRunnableTaskFromQueueAndRemove(eventLoop.taskQueue);
     
    if (oldestTask !== null) {
      // 2.2. Set taskStartTime to the unsafe shared current time.
      taskStartTime = Date.now();
       
      // 2.4. Set the event loop's currently running task to oldestTask.
      eventLoop.currentRunningTask = oldestTask;
       
      // 2.5. Perform oldestTask's steps.
      performTaskSteps(oldestTask.steps);
       
      // 2.6. Set the event loop's currently running task back to null.
      eventLoop.currentRunningTask = null;
       
      // 2.7. Perform a microtask checkpoint.
      performMicrotaskCheckpoint(eventLoop);
    }
     
    // 3. Let hasARenderingOpportunity be false.
    let hasARenderingOpportunity = false;
     
    // 4. Let `now` be the unsafe shared current time.
    let now = Date.now();
     
    // 5. If oldestTask is not null, then:
    if (oldestTask !== null) {
      // 5.1. Let top-level browsing contexts be an empty set.
      const topLevelBrowsingContexts = new Set();
       
      // 5.2. For each environment settings object settings of oldestTask's script evaluation
      //      environment settings object set:
      oldestTask.environmentSettingsObject.forEach((settingsObject) => {
       
      // 5.2.1. Let `global` be settings's global object.
      const global = settingsObject.targetBrowsingContext;
       
      // 5.2.2. If `global` is not a Window object, then continue.
      if (!(global instanceof Window)) {
        return;
      }
       
      // 5.2.3. If global's browsing context is null, then continue.
      if (!global.document) {
        return;
      }
       
      // 5.2.4. Let tlbc be global's browsing context's top-level browsing context.
      const tlbc = global.document;
       
      // 5.2.5. If tlbc is not null, then append it to top-level browsing contexts.
      if (tlbc !== null) {
        topLevelBrowsingContexts.add(tlbc)
      }
    });
     
    // 5.3. Report long tasks, passing in taskStartTime, now (the end time of the task), top-level browsing contexts,
    //      and oldestTask.
    //      https://w3c.github.io/longtasks/#report-long-tasks
    // ...
  }
   
  // 6. if this is a window event loop, then: Update the rendering
  if (isWindowEventLoop(eventLoop)) {
    updateRendering(eventLoop);
  }
   
  // 7. If all of the following are true:
  //   - this is a window event loop;
  //   - there is no task in this event loop's task queues whose document is fully active;
  //   - this event loop's microtask queue is empty; and
  //   - hasARenderingOpportunity is false,
  // then:
  //   ...run computeDeadline and hasPendingRenders steps for WindowEventLoop
   
  // 8. If this is a WorkerEventLoop, then:
  //   ...run animation frame callbacks and update the rendering of that dedicated worker
}

根据规范,一些操作可以转移到单独的函数和算法中。例如,步骤 2.7 执行微任务检查点已被转移到一个名为 performMicrotaskCheckpoint 的单独函数中。

/** Finds and returns the first runnable task in the queue. The found Task will be removed from the queue */
function getFirstRunnableTaskFromQueueAndRemove(taskQueue: Set<Task>): Task | null {
  //...
  return null;
}

/** Performs Task steps */
function performTaskSteps(steps: Steps) {
  //...
}

/**
 * Performs a microtask checkpoint
 * https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
 */
function performMicrotaskCheckpoint(eventLoop: EventLoop) {
  // 1. If the event loop's performing a microtask checkpoint is true, then return.
  if (eventLoop.performingAMicrotaskCheckpoint) {
    return;
  }
  
  // 2. Set the event loop's performing a microtask checkpoint to true.
  eventLoop.performingAMicrotaskCheckpoint = true;
  
  // 3. While the event loop's microtask queue is not empty:
  while (eventLoop.microtaskQueue.length > 0) {
    // 3.1. Let oldestMicrotask be the result of dequeuing from the event loop's microtask queue.
    const oldestMicrotask = eventLoop.microtaskQueue.shift();
    
    // 3.2. Set the event loop's currently running task to oldestMicrotask.
    eventLoop.currentRunningTask = oldestMicrotask;
    
    // 3.3. Run oldestMicrotask.
    performTaskSteps(oldestMicrotask.steps);
    
    // 3.4. Set the event loop's currently running task back to null.
    eventLoop.currentRunningTask = null;
  }
  
  // 4. For each environment settings object whose responsible event loop is this event loop, notify about rejected
  //    promises on that environment settings object.
  // ...
  
  // 5. Cleanup Indexed Database transactions.
  // ...
  
  // 6. Perform ClearKeptObjects().
  // ...
  
  // 7. Set the event loop's performing a microtask checkpoint to false.
  eventLoop.performingAMicrotaskCheckpoint = false;
}

/**
 * Runs `Update the rendering` steps for WindowEventLoop
 * https://html.spec.whatwg.org/multipage/webappapis.html#update-the-rendering
 */
function updateRendering(eventLoop: WindowEventLoop) {
  // ... reveal that Document
  // ... flush autofocus candidates for that Document
  // ... run the resize steps for that Document
  // ... run the scroll steps for that Document
  // ... evaluate media queries and report changes for that Document
  // ... update animations and send events for that Document
  // ... run the fullscreen steps for that Document
  // ... run the animation frame callbacks for that Document
  // ... if the focused area of that Document is not a focusable area, then run the focusing steps for that
  //     Document's viewport
  // ... perform pending transition operations for that Document
  // ... run the update intersection observations steps for that Document
  // ... invoke the mark paint timing algorithm
  // ... update the rendering or user interface of that Document and its node navigable
  // ... run process top layer removals given Document.
}

总结:

  • 事件循环存在,但超出了 ECMA-262 规范的责任范围。
  • 在浏览器的环境中,事件循环的官方信息来源可以被认为是 HTML 规范;
  • 在非浏览器的环境中,官方文档或者 HOST 执行器自身的官方文档可以被认为是事件循环的官方信息来源。
  • ECMA-262 规范间接涉及与事件循环相关的流程,将这些流程的实现留给了 HOST 执行器自行决定。
  • 事件循环并不仅仅与维护 JavaScript 代码有关。事实上,JavaScript 只是可以进入事件循环的任务类型之一。除了 JavaScript,浏览器还可以将其他任务放置在这里,例如标记接收到的 HTML 文本、处理输入/输出操作、在屏幕上渲染元素等等。
  • 根据 HTML 规范,事件循环不必是线程安全的,但可以是。在执行多个代理并将它们的事件循环放置在同一个线程的情况下,它们必须组织彼此之间的交互算法,以确保在任一时刻只有一个代理作为未阻塞代理出现,而其他代理必须处于阻塞状态。
  • 事件循环由任务队列和微任务队列组成。由 HOST 执行器分配的任务被放置到任务队列中。微任务队列是任务队列中的任务使用执行器的 Web API 执行其特定的异步子任务的一个可选机会。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 155,348评论 4 355
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 65,906评论 1 283
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 105,191评论 0 235
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,295评论 0 201
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,606评论 3 283
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,107评论 1 202
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,507评论 2 305
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,217评论 0 193
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,848评论 1 234
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,183评论 2 238
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,734评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,084评论 2 247
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,608评论 3 228
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,921评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,627评论 0 190
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,103评论 2 261
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,054评论 2 257

推荐阅读更多精彩内容