服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - 编程技术 - EventLoop = TaskQueue + RenderQueue,你看明白了吗?

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

2023-12-08 13:26前端柒八九 编程技术

在最近的工作和学习中,有一个词总是在眼前挥之不去--EventLoop。而在之前,其实我们讲过相关的内容,Event Loop 可视化解析

前言

在最近的工作和学习中,有一个词总是在眼前挥之不去--EventLoop。而在之前,其实我们讲过相关的内容,Event Loop 可视化解析

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

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

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

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

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

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

我们能所学到的知识点

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

1. 前置知识点

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

页面刷新术语

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

  • 「屏幕刷新频率」

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

  • 「逐行扫描」
  • 显示器并不是一次性将画面显示到屏幕上,而是「从左到右边,从上到下逐行扫描」,顺序显示整屏的一个个像素点,不过这一过程快到人眼无法察觉到变化。

  • 以 60 Hz 刷新率的屏幕为例,这一过程即 1000 / 60 ≈ 16ms。

  • 当扫描完一个屏幕后,设备需要「重新回到第一行」以进入下一次的循环,此时有一段时间空隙,称为VerticalBlanking Interval(VBI)。

  • 「帧率 (Frame Rate)」

  • 表示 「GPU 在一秒内绘制操作的帧数」,如 60 fps,即每秒钟GPU最多绘制 60 帧画面。

  • 帧率是「动态变化」的,例如当画面静止时,GPU 是没有绘制操作的,屏幕刷新的还是buffer中的数据,即GPU最后操作的帧数据。

  • 「画面撕裂(tearing)」

  • 一个屏幕内的数据来自2个不同的帧,画面会出现撕裂感。

测试帧率

我们可以借助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]中实验的结果。

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

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

WebAPI

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

APIs

描述

网络请求

(Network Requests)

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


DOM操作

(DOM Manipulation)

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

事件处理

(Event Handling)

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

存储机制

(Storage Mechanisms)

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

设备API

(Device APIs)

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

图形和动画

(Graphics & Animation)

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

性能监控

(Performance Monitoring)

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

其他API

(Other APIs)

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

WebAPI的工作流程

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

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

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

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

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):
  • 使用CSS动画和过渡属性,例如transform属性的过渡,可以触发GPU硬件加速。例如:

    .element {
      transition: transform 0.3s ease-in-out;
    }

  1. Canvas 绘图:

  • 元素上进行绘图操作通常会利用GPU硬件加速。这包括使用2D或WebGL上下文进行图形渲染。

  1. 使用 will-change 属性:

  • will-change属性告诉浏览器某个属性将会被改变,从而可以提前进行优化。例如:

    .element {
      will-change: transform;
    }

  1. 使用 image-rendering 属性:

  • image-rendering属性用于指定图像的渲染质量,而且在某些情况下也能触发GPU硬件加速。例如:

    .element {
      image-rendering: pixelated;
    }

  1. 使用 backface-visibility 属性:

  • backface-visibility属性用于指定当元素不面向屏幕时是否可见。在某些情况下,该属性的使用可以触发GPU硬件加速。例如:

    .element {
      backface-visibility: hidden;
    }

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

  • 在某些情况下,使用filter属性(如模糊、对比度等)可能触发GPU硬件加速。

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

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

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

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

2. 事件循环(Event Loop)

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

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

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

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

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

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

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


任务触发器

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

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

延伸 · 阅读

精彩推荐