文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

EventLoop = TaskQueue + RenderQueue,你看明白了吗?

2024-11-30 04:20

关注

图片

上文我们从偏JS调用机制的角度分析了,调用栈(Call Stack)/宏任务队列 (Task Queue)和微任务队列 (Microtask Queue)他们之间的关系和他们是如何协同合作的。并且,举了很多例子,用可视化的方式讲解它们如何工作的。

而今天,我们从浏览器内部的实现细节来谈谈EventLoop是如何从接受任务到渲染出对应页面的。

也就是下图中所涉及到的各个重要节点。在阅读完本文后,希望大家能对下面有一个清晰的认知。

图片

好了,天不早了,干点正事哇。

我们能所学到的知识点

  1. 前置知识点
  2. 事件循环(Event Loop)
  3. 任务队列/微任务队列/调用栈
  4. 在渲染队列中执行的是什么?
  5. EventLoop模型

1. 前置知识点

「前置知识点」,只是做一个概念的介绍,不会做深度解释。因为,这些概念在下面文章中会有出现,为了让行文更加的顺畅,所以将本该在文内的概念解释放到前面来。「如果大家对这些概念熟悉,可以直接忽略」同时,由于阅读我文章的群体有很多,所以有些知识点可能「我视之若珍宝,尔视只如草芥,弃之如敝履」。以下知识点,请「酌情使用」。

页面刷新术语

我们在页面是如何生成的(宏观角度)一文中提到过这些指标,这里就拿来主义了。

一秒内屏幕刷新的次数(一秒内显示了多少帧的图像),单位 Hz(赫兹),如常见的 60 Hz。「刷新频率取决于硬件的固定参数」(不会变的)。

测试帧率

我们可以借助requestAnimationFrame通过每个测量前后帧发生的时间间隔,来从侧面查看本地浏览器帧率。

const checkRequestAnimationDiff = () => {
    let prev;
    function call() {
        requestAnimationFrame((timestamp) => {
            if (prev) {
                console.log(timestamp - prev); 
                // 应该大约是60FPS的16.6毫秒
            }
            prev = timestamp;
            call();
        });
    }
    call();
}
checkRequestAnimationDiff();

随意打开一个网站,并将上述代码贴到devtool-Console运行。

下面是,我们在React-官网[1]中实验的结果。

图片

从输出结果来看,虽然结果不是唯一,但是它们的值都稳定在16.67~16.68。和我们60fps是吻合的。

WebAPI

WebAPI工作的原理依赖于浏览器作为宿主环境来提供和执行这些API。在Web开发中,我们通常指的WebAPI是「浏览器内置的API」,它们允许开发者利用JavaScript与浏览器的功能进行交互。

APIs

描述

网络请求

(Network Requests)

使用XMLHttpRequestfetch API,可以发起异步的HTTP请求到服务器,并在不刷新页面的情况下获取或发送数据。


DOM操作

(DOM Manipulation)

浏览器提供了一套DOM API,允许JavaScript访问和操作页面上的元素。比如,可以添加、删除或更改元素,或者修改元素的样式和内容。

事件处理

(Event Handling)

WebAPI允许注册事件处理程序来响应用户行为(如点击、滑动)或浏览器事件(如页面加载、窗口尺寸变化)。

存储机制

(Storage Mechanisms)

浏览器提供了如localStoragesessionStorageIndexedDB等API,可以在用户的设备上存储数据。

设备API

(Device APIs)

可以访问设备的硬件,如摄像头、麦克风、地理位置等,这通常通过navigator对象暴露的API实现。

图形和动画

(Graphics & Animation)

CanvasWebGL API允许在网页上绘制二维和三维图形。requestAnimationFrame为动画提供了一个优化的循环机制。

性能监控

(Performance Monitoring)

Performance API提供了获取浏览器性能相关数据的接口,帮助开发者监控和优化网页性能。

其他API

(Other APIs)

还有诸如Web Audio APIWebRTCWebSocket等,使得在网页上实现复杂的音频处理、实时通信成为可能。

WebAPI的工作流程

  1. 「调用API」:开发者在JavaScript代码中调用某个WebAPI。
  2. 「浏览器解释执行」: 浏览器解释JavaScript代码,并「执行相应的API调用」。
  3. 「API内部处理」:WebAPI内部可能会执行多种操作,如触发网络请求、访问数据库、启动硬件设备等。
  4. 「回调和事件循环」:对于异步操作,WebAPI通常会使用回调函数或Promise来处理操作完成后的结果。浏览器的事件循环机制确保了这些回调在适当的时候被调用。
  5. 「渲染和更新」:对于涉及视觉变化的API,如DOM操作或Canvas绘图,浏览器会更新页面内容,这通常发生在浏览器的下一个重绘周期。

在整个过程中,「浏览器的角色是中介」,它提供了执行API的环境和必要的安全措施。这些API让Web应用可以像本地应用一样丰富和强大,同时仍然运行在浏览器这个相对安全的沙箱环境中。

下面的图,展示了WebAPI的地位(中间部分)。

图片

GPU硬件加速

「GPU(Graphics Processing Unit)硬件加速」是一种利用GPU来执行图形和计算任务的技术。在Web开发中,GPU硬件加速可以通过利用用户计算机中的GPU资源来加速浏览器的渲染和绘制操作。这通常可以提高网页的性能和流畅度,尤其是对于需要大量图形操作的页面。

在Web开发中,一些CSS属性和操作可以触发GPU硬件加速,以便更有效地利用GPU资源。

  1. 3D 变换(transform)

使用transform属性进行3D变换,如translate3d、rotate3d、scale3d等,可以触发GPU硬件加速。例如:

.element {
  transform: translate3d(0, 0, 0);
}
  1. CSS 动画(animation)和过渡(transition):
  1. Canvas 绘图:

  1. 使用 will-change 属性:

  1. 使用 image-rendering 属性:

  1. 使用 backface-visibility 属性:

  1. 使用 filter 属性(某些情况下):

还记得我们在你会在浏览器中打断点吗?我会!中介绍过如何看chromium 在线仓库[2]

那我们就从源码的角度来看看,为什么上面的属性会走GPU硬件加速

或者我们可以看compositing_reason_finder.cc这个文件,它例举了很多枚举类型。

图片

2. 事件循环(Event Loop)

事件循环就是一个「死循环」,不死不休。

旧的操作系统不支持多线程,它们的事件循环可以被大致描述为一个简单的循环:

while (true) {
    if (hasTasks()) {
        executeTask();
    }
}

现代操作系统的调度器(schedulers)非常复杂。它们有优先级设置、执行队列等许多其他技术。

这里做一个题外话,看到schedulers/优先级设置是不是想到React-Fiber架构了。其实,React在内部就是模仿操作系统,做了自己的实现逻辑。(这里就不展开说明了)

为了让事情简单化,我们可以将事件循环(Event Loop)描述为一个循环,该循环检查是否有任何待处理的任务:

图片


任务触发器

浏览器属于「事件驱动」的技术框架,如果想让Event Loop探查并执行对应的任务,首先要做的就是将某些任务进行触发。也就是唤起指定任务的触发器。

下面就是我们平时能够接触到的任务触发器