Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

理解 Event loops(浏览器) #24

Open
yangdui opened this issue May 18, 2020 · 0 comments
Open

理解 Event loops(浏览器) #24

yangdui opened this issue May 18, 2020 · 0 comments

Comments

@yangdui
Copy link
Owner

yangdui commented May 18, 2020

理解 Event loops

在本章以 whatwg 中的 HTML Standard 规定为标准(w3c 已经和 whatwg 签署协议,达成合作)。

定义

To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section.

为了协调事件、用户交互、脚本、UI 渲染、网络请求,用户代理必须使用 eventloop。

定义中只是表明了 event loop 的作用:协调事件、用户交互等,也就是在 JavaScript 的单线程中,异步处理回调,保证不会阻塞。实际浏览器是多线程,采用了多线程保证不阻塞。比如我们平常使用的 setTimeout 函数使用了定时器触发线程。关于浏览器以后有机会在介绍。

在 ECMAScript 中有 Jobs and Job QueuesRunJobs ( ) ,这里规定是的是 JavaScript 引擎(比如说 chrome 中的 V8 引擎)独立运行时,需要用到这里的规定来处理 Promise 异步问题。这个规定是处理 JavaScript 自己函数的异步问题。而 event loop 是属于 JavaScript Runtime的,是需要嵌入到宿主环境的(比如浏览器和 Nodejs),在宿主环境中,不只是 JavaScript 自己有异步,还有环境中其他异步。这些异步就是定义中说的事件、用户交互、脚本、UI渲染、网络请求。所以在 HTML Standard 中的 8.1.3.7 Integration with the JavaScript job queue 中明确提到在用户代理中,使用 HTML 文档中的 Event loops,而 ECMAScript 中的 Promise 集成到 event loop 中。

**注:**Nodejs 中的 event loop 单独介绍。

任务队列 task

task queue(macrotask)

一个 event loop 有一个或多个 task queues。这里的 task 就是 macrotask,在规定中没有明确提出 macrotask ,task queue 中存放不同任务源的 task。常见的任务源

  • DOM 操作(The DOM manipulation task source)
  • 用户交互(The user interaction task source)
  • 网络任务(The networking task source)
  • history 任务(The history traversal task source)

对于 task :

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.

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

来自相同任务源的 task 放在同一 task queue 中,不同任务源的 task 放在不同 task queue 中。

同一 task queue 中的 task 顺序执行,不过 task queue 中的 task 浏览器(宿主环境)根据实际情况进行调度。

一般来说,鼠标、键盘 task queue 中的 task 优先于其他 task queue 中的 task,目的是保证用户体验。

microtask queue

一个 event loop 只有一个 microtask queue。

microtask queue 的任务源规范中没有明确规定,一般就是:

  • Promise
  • MutationObserver

event loop 处理过程

在 HTML 规范中 8.1.4.3 Processing model 有明确规定,这里做一个概括:

  1. 从 task queue 中选出一个进入队列时间最长的 task(oldestTask)执行。
  2. 检查 microtask queue 中是否有 task,如果有则执行直到 microtask queue 为空。
  3. 如果在 window event loop 中,处理 Update the rendering 。

一般把执行全局 JavaScript 当做一个 task。

microtask checkpoint

在 event loop 处理过程中,对于 microtask 有一个很重要的步骤就是 microtask checkpoint

When a user agent is to perform a microtask checkpoint:

  1. If the event loop's performing a microtask checkpoint is true, then return.

  2. Set the event loop's performing a microtask checkpoint to true.

  3. While the event loop's microtask queue is not empty:

    1. Let oldestMicrotask be the result of dequeuing from the event loop's microtask queue.

    2. Set the event loop's currently running task to oldestMicrotask.

    3. Run oldestMicrotask.

      This might involve invoking scripted callbacks, which eventually calls the clean up after running script steps, which call this perform a microtask checkpoint algorithm again, which is why we use the performing a microtask checkpoint flag to avoid reentrancy.

    4. Set the event loop's currently running task back to 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. Set the event loop's performing a microtask checkpoint to false.

performing a microtask checkpoint 为 false 才执行 microtask checkpoint。如果为 true ,直接返回。

如果 microtask queue 不为空,选出 oldestMicrotask 执行,在执行 oldestMicrotask 有可能触发 Calling scripts 的清理阶段(clean up after running script),在清理阶段也会执行 microtask 检查任务。所以一开始需要检查 performing a microtask checkpoint 状态。

microtask 执行时机

在 event loop 中,microtask 是在一个 task 执行完之后在执行,但是在 HTML 文档中,有很多处调用 microtask checkpoint:

执行 microtask 前提是:执行栈必须为空,且没有正在运行的执行上下文。

有一个很好的例子:

html 代码

<div id="outer">
     aaa
    <div id="inner">bbb</div>
</div>

JavaScript 代码

const inner = document.getElementById("inner");
const outer = document.getElementById("outer");

// 监听 outer 的属性变化。
new MutationObserver(() => console.log("mutate outer"))
    .observe(outer, { attributes: true });

// 处理 click 事件。
function onClick()
{
    console.log("click");
    setTimeout(() => console.log("timeout"), 0);
    Promise.resolve().then(() => console.log("promise"));
    outer.setAttribute("data-mutation", Math.random());
}

// 监听 click 事件。
inner.addEventListener("click", onClick);
outer.addEventListener("click", onClick);
// inner.click();

第一种情况是:点击 id 为 inner 的区域,结果如下:

click
promise
mutate outer
click
promise
mutate outer
timeout
timeout

第二种情况是:添加 inner.click(),通过 JavaScript 代码触发回调,结果如下:

click
click
promise
mutate outer
promise
timeout
timeout

两种情况有明显差别,根本原因就是:两种情况下,执行栈情况不同,第二种情况 inner.click 在回调函数执行完(回调函数中的异步没有执行)之前,一直存在执行栈中,这种情况下是不会检查 microtask 队列的。

详细解释可以看这里

Update the rendering (更新渲染)

这里只介绍 event loop 中 Update the rendering 的内容,不会涉及浏览器具体怎么更新渲染。

event loop 处理过程中,在最后会进行 Update the rendering ,但并不是每一次 event loop 都会执行 Update the rendering,浏览器会做一个综合判断,更新渲染是否获利,保证 60Hz 的刷新率等。

Update the rendering 总体来说有两个大的步骤:判断是否更新、更新时处理的各种事件。

在更新渲染阶段处理的事件如下:

  • resize 事件
  • scroll 事件
  • media query
  • animations 及其回调
  • fullscreen 事件
  • intersection observations(IntersectionObserver)
  • mark paint timing
  • flush autofocus candidates(自动对焦是在更新渲染完成后触发的)

参考资料

HTML 文档 event loops

深入理解 JavaScript Event Loop

跟着 Event loop 规范理解浏览器中的异步机制

JavaScript 中的 task queues

深入探究 eventloop 与浏览器渲染的时序问题

从event loop规范探究javaScript异步及浏览器更新渲染时机

ECMAScript 的 Job Queues 和 Event loop 有什么关系?

浏览器渲染详细过程:重绘、重排和 composite 只是冰山一角

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant