前言
读完本文您将了解以下几点
1,浏览器内有哪些进程,这些进程都有些什么作用。
2,浏览器地址输入 URL 后,内部的进程、线程都做了哪些事,以及我们与浏览器交互时,内部进程是怎么处理这些交互事件的。
浏览器架构
进程和线程
1,进程(process)是程序的一次执行过程,是一个动态概念,是程序在执行过程中分配和管理资源的基本单位。
2,线程(thread)是 CPU 调度和分派的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
简单的说,进程可以理解成正在执行的应用程序,而线程呢,可以理解成我们应用程序中的代码的执行器。而他们的关系可想而知,线程是跑在进程里面的,一个进程里面可能有一个或者多个线程,而一个线程,只能隶属于一个进程。
浏览器属于一个应用程序,而应用程序的一次执行,可以理解为计算机启动了一个进程,进程启动后,CPU 会给该进程分配相应的内存空间,当进程得到了内存之后,就可以使用线程进行资源调度,进而完成应用程序的功能。
在应用程序中,为了满足功能的需要,启动的进程会创建另外的新的进程来处理其他任务,这些创建出来的新的进程拥有全新的独立的内存空间,不能与原来的进程共享内存,如果这些进程之间需要通信,可以通过 IPC 机制(Inter Process Communication)来进行。
很多应用程序都会采取这种多进程的方式来工作,因为进程和进程之间是互相独立的它们互不影响,也就是说,当其中一个进程挂掉了之后,不会影响到其他进程的执行,只需要重启挂掉的进程就可以恢复运行。
浏览器的多进程架构
1,开发一个浏览器,它的架构可以是一个单进程多线程的应用程序,也可以是一个使用 IPC 通信的多进程应用程序。不同的浏览器使用不同的架构,下面主要以 Chrome 为例,介绍浏览器的多进程架构。在 Chrome 中,主要的进程有 4 个:
浏览器进程 (Browser Process)
:负责浏览器的 Tab 的前进、后退、地址栏、书签栏的工作和处理浏览器的一些不可见的底层操作,比如网络请求和文件访问。渲染进程 (Renderer Process)
:负责一个 Tab 内的显示相关的工作,也称渲染引擎。插件进程 (Plugin Process)
:负责控制网页使用到的插件。GPU(图形处理器)进程 (GPU Process)
:负责处理整个应用程序的 GPU 任务。
GPU:显卡的处理器称为图形处理器(GPU),它是显卡的”心脏”,与 CPU 类似,只不过 GPU 是专为执行复杂的数学和几何计算而设计的。
CPU:CPU 一般是指中央处理器,它是一块超大规模的集成电路,是一台计算机的运算核心和控制核心。它的功能主要是解释计算机指令以及处理计算机软件中的数据。
CPU 与 GPU 的区别:当你操作电脑的时候,为了完成某项工作,需要电脑帮你工作,就像计算某个题目那样。计算题目,理解题目并且整理出解题的步骤以及解法,那是 CPU 的事情。但是解题的过程需要用到的众多计算,则需要一帮不需要很高逻辑理解力的计算者完成,他们只需要负责其中很简单但是数量又很大的简单运算就行了,最后他们把各自运算的结果交出来给 CPU 整理,而这群计算者就是 GPU。
上图中对 CPU 与 GPU 中的逻辑架构进行了对比。其中 Control 是控制器、ALU 算术逻辑单元、Cache 是 cpu 内部缓存、DRAM 就是内存。可以看到 GPU 设计者将更多的晶体管用作执行单元,而不是像 CPU 那样用作复杂的控制单元和缓存。从实际来看,CPU 芯片空间的 5%是 ALU,而 GPU 空间的 40%是 ALU。这也是导致 GPU 计算能力超强的原因。
2,以上 4 个进程之间的关系:
- 当我们浏览一个网页,我们会在浏览器的地址栏里输入 URL,这个时候 Browser Process 会向这个 URL 发送请求,获取这个 URL 的 HTML 内容,然后将 HTML 交给 Renderer Process,Renderer Process 解析 HTML 内容,解析遇到需要请求网络的资源又返回来交给 Browser Process 进行加载,同时通知 Browser Process,需要 Plugin Process 加载插件资源,执行插件代码。解析完成后,Renderer Process 计算得到图像帧,并将这些图像帧交给 GPU Process,GPU Process 将其转化为图像显示屏幕。
多进程架构的好处
1,更高的容错性。当今 WEB 应用中,HTML,JavaScript 和 CSS 日益复杂,这些跑在渲染引擎的代码,频繁的出现 BUG,而有些 BUG 会直接导致渲染引擎崩溃,多进程架构使得每一个渲染引擎运行在各自的进程中,相互之间不受影响,也就是说,当其中一个页面崩溃挂掉之后,其他页面还可以正常的运行不收影响。
2,更高的安全性和沙盒性(sanboxing)。渲染引擎会经常性的在网络上遇到不可信、甚至是恶意的代码,它们会利用这些漏洞在你的电脑上安装恶意的软件,针对这一问题,浏览器对不同进程限制了不同的权限,并为其提供沙盒运行环境,使其更安全更可靠。
3,更高的响应速度。在单进程的架构中,各个任务相互竞争抢夺 CPU 资源,使得浏览器响应速度变慢,而多进程架构正好规避了这一缺点。
多进程架构优化
1,前面说到,Renderer Process 的作用是负责一个 Tab 内的显示相关的工作,这就意味着,一个 Tab,就会有一个 Renderer Process,这些进程之间的内存无法进行共享,而不同进程的内存常常需要包含相同的内容。
浏览器的进程模式
1,为了节省内存,Chrome 提供了四种进程模式(Process Models),不同的进程模式会对 Tab 进程做不同的处理。
Process-per-site-instance(default):同一个 site-instance 使用一个进程。
Process-per-site:同一个 site 使用一个进程。
Process-per-tab:每个 tab 使用一个进程。
Single process:所有 tab 共用一个进程。
2,site 和 site-instance 的定义:
site 指的是相同的 registered domain name(如:google.com ,bbc.co.uk)和 scheme (如:https://)。比如 a.baidu.com 和 b.baidu.com,即只有主机不同,就可以理解为同一个 site(注意这里要和 Same-origin policy(同源策略)区分开来,因为同源策略还涉及到子域名和端口)。
site-instance 指的是一组 connected pages from the same site(来自同一站点的已连接页面),这里 connected 的定义是 can obtain references to each other in script code(可以在脚本代码中获取彼此的引用)怎么理解这段话呢,满足下面两中情况并且打开的新页面和旧页面属于上面定义的同一个 site,就属于同一个 site-instance(网站实例)。
用户通过
_<a target="\_blank">
这种方式点击打开的新页面。JS 代码打开的新页面(比如 window.open)。
浏览器四个进程模式说明
1,Single process:指单进程模式,所有 tab 都会使用同一个进程。
2,Process-per-tab:每打开一个 tab,会新建一个进程。
3,Process-per-site:当你打开 a.baidu.com 页面,在打开 b.baidu.com 的页面,这两个页面的 tab 使用的是同一个进程,因为这两个页面的 site 相同,而如此一来,如果其中一个 tab 崩溃了,而另一个 tab 也会崩溃。
4,Process-per-site-instance:这种模式是最重要的,因为这个是 Chrome 默认使用的模式,也就是几乎所有的用户都在用的模式。当你打开一个 tab 访问 a.baidu.com ,然后再打开一个 tab 访问 b.baidu.com,这两个 tab 会使用两个进程。而如果你在 a.baidu.com 中,通过 JS 代码打开了 b.baidu.com 页面,这两个 tab 会使用同一个进程。
默认模式选择
1,那么为什么浏览器使用 Process-per-site-instance 作为默认的进程模式呢?
- Process-per-site-instance 兼容了性能与易用性,是一个比较中庸通用的模式。相较于 Process-per-tab,能够少开很多进程,就意味着更少的内存占用;相较于 Process-per-site,能够更好的隔离相同域名下毫无关联的 tab,更加安全。
导航过程都发生了什么
网页加载过程
工作线程
1,上面提到,tab 以外的大部分工作由浏览器进程 Browser Process(浏览器进程)负责,针对工作的不同,Browser Process 划分出不同的工作线程:
UI thread:控制浏览器上的按钮及输入框。
network thread:处理网络请求,从网上获取数据。
storage thread:控制文件等的访问。
网页加载步骤
1,处理输入:当我们在浏览器的地址栏输入内容按下回车时,UI thread 会判断输入的内容是搜索关键词(search query)还是 URL,如果是搜索关键词,跳转至默认搜索引擎对应都搜索 URL,如果输入的内容是 URL,则开始请求 URL。
2,开始导航:回车按下后,UI thread 将关键词搜索对应的 URL 或输入的 URL 交给网络线程 Network thread,此时 UI 线程使 Tab 前的图标展示为加载中状态,然后网络进程进行一系列诸如 DNS 寻址,建立 TLS 连接等操作进行资源请求,如果收到服务器的 301 重定向响应,它就会告知 UI 线程进行重定向然后它会再次发起一个新的网络请求。
3,读取响应:network thread 接收到服务器的响应后,开始解析 HTTP 响应报文,然后根据响应头中的 Content-Type 字段来确定响应主体的媒体类型(MIME Type),如果媒体类型是一个 HTML 文件,则将响应数据交给渲染进程(renderer process)来进行下一步的工作,如果是 zip 文件或者其它文件,会把相关数据传输给下载管理器。与此同时,浏览器会进行 Safe Browsing 安全检查,如果域名或者请求内容匹配到已知的恶意站点,network thread 会展示一个警告页。除此之外,网络线程还会做 CORB(Cross Origin Read Blocking)检查来确定那些敏感的跨站数据不会被发送至渲染进程。
CORB:chromium 文档中对它的定义是:浏览器在加载可以跨域资源时,在资源载入页面之前,对其进行识别和拦截的算法。
具体说明:Chrome 浏览器在处理不同 tab 和不同页面时,会将为它们划分不同的进程,而且受制于同源策略的影响,这些页面之间本应该互不干扰。但是我们知道,同源策略虽然牛逼,但浏览器中仍然存在一些不受制于它约束的 api、标签,比如常见的 img、iframe 和 script 等等。诸如以下代码,不知道看文章的诸位有没有写过,或者说遇见过:
<img src="https://foo/bar/baz/">
。有人可能会问,一个 img 标签你 src 属性不填图片的 uri,你是不是傻。其实不是这样的,有时候对网站做一些跟踪和分析时,确实会这么写,因为浏览器会往
https://foo/bar/baz/
这个地址发送一个 GET 资源的请求,在服务端我们可以利用这个请求做一些追踪的逻辑,同理 script 也可以完成需求。但是这么做的后果就是,虽然 img 帮我们发送了这个请求,但是它却没有得到所期望格式的资源,所以这里实际可以算作一种错误或者异常。而一些攻击者可以利用这一点,比如,在页面嵌入下面的代码:<img src="https://example.com/secret.json">
来加载跨域私密文件,因为 img 不受同源策略的制约,这个请求是可以发出去的,服务器响应返回后,显然 secret.json 不是一个图片格式的资源,img 不会显示它,但是并不代表负责渲染当前页面的进程的内存中没有保留关于 secret.json 的数据。因此攻击者可以利用上文中提及的漏洞,编写一些代码来“偷”这些数据,从而造成安全隐患。CORB 的作用:就是当浏览器尝试以上面代码的方式加载跨域资源时,在资源未被加载之前进行拦截,从而提升攻击者进行幽灵攻击的成本,这里之所以是说提升成本还非彻底解决是因为这个漏洞是基于硬件层面的,所以软件层面只能做有限的修复,有的人可能马上会说,那 CPU 直接去掉或者用户放弃使用预处理功能不就好了吗?理论上是这样的,但是这将导致预处理带来的性能红利瞬间消失,而且 CPU 的架构设计也不是一天两天就能改的,而且就算改了也没办法一下普及。
- 受 CORB 保护的内容类型:分别是 json、html 和 xml。关于如何针对每种内容类型 CORB 如何对其进行保护,文档中有详细的章节进行介绍,这里就做过多的赘述了。大体的规则均是对内容格式进行一些有针对性的校验,以确认它确实是某个内容类型。这个校验结果最终影响 CORB 的运作方式。
4,查找渲染进程:各种检查完毕以后,network thread 确信浏览器可以导航到请求网页,network thread 会通知 UI thread 数据已经准备好,UI thread 会查找到一个 renderer process 进行网页的渲染。
- 浏览器为了对查找渲染进程这一步骤进行优化,考虑到网络请求获取响应需要时间,所以在第二步开始,浏览器已经预先查找和启动了一个渲染进程,如果中间步骤一切顺利,当 network thread 接收到数据时,渲染进程已经准备好了,但是如果遇到重定向,这个准备好的渲染进程也许就不可用了,这个时候会重新启动一个渲染进程。
5,提交导航:到了这一步,数据和渲染进程都准备好了,Browser Process 会向 Renderer Process 发送 IPC 消息来确认导航,此时,浏览器进程将准备好的数据发送给渲染进程,渲染进程接收到数据之后,又发送 IPC 消息给浏览器进程,告诉浏览器进程导航已经提交了,页面开始加载。
- 这个时候导航栏会更新,安全指示符更新(地址前面的小锁),访问历史列表(history tab)更新,即可以通过前进后退来切换该页面。
IPC:IPC(Inter-Process Communication,进程间通信)。进程间通信是指在不同进程之间传播或交换信息。
- IPC 的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams 等。其中 Socket 和 Streams 支持不同主机上的两个进程 IPC。
6,初始化加载完成:当导航提交完成后,渲染进程开始加载资源及渲染页面(详细内容下文介绍),当页面渲染完成后(页面及内部的 iframe 都触发了 onload 事件),会向浏览器进程发送 IPC 消息,告知浏览器进程,这个时候 UI thread 会停止展示 tab 中的加载中图标。
网页渲染原理
具体原理说明
1,导航过程完成之后,浏览器进程把数据交给了渲染进程,渲染进程负责 tab 内的所有事情,核心目的就是将 HTML/CSS/JS 代码,转化为用户可进行交互的 web 页面。那么渲染进程是如何工作的呢?
2,渲染进程中,包含线程分别是:
一个主线程(main thread)。
多个工作线程(work thread)。
一个合成器线程(compositor thread)。
多个光栅化线程(raster thread)。
- 以上不同的线程,有着不同的工作职责。
构建 DOM
1,当渲染进程接受到导航的确认信息后,开始接受来自浏览器进程的数据,这个时候,主线程会解析数据转化为 DOM(Document Object Model)对象。
子资源加载
1,在构建 DOM 的过程中,会解析到图片、CSS、JavaScript 脚本等资源,这些资源是需要从网络或者缓存中获取的,主线程在构建 DOM 过程中如果遇到了这些资源,逐一发起请求去获取,而为了提升效率,浏览器也会运行预加载扫描(preload scanner)程序,如果 HTML 中存在 img、link 等标签,预加载扫描程序会把这些请求传递给 Browser Process 的 network thread 进行资源下载。
JavaScript 的下载与执行
1,构建 DOM 过程中,如果遇到 script 标签,渲染引擎会停止对 HTML 的解析,而去加载执行 JS 代码,原因在于 JS 代码可能会改变 DOM 的结构(比如执行 document.write()等 API)。
2,开发者其实也有多种方式来告知浏览器应对如何应对某个资源,比如说如果在 script 标签上添加了 async 或 defer 等属性,浏览器会异步的加载和执行 JS 代码,而不会阻塞渲染。
样式计算(Style calculation)
1,DOM 树只是我们页面的结构,我们要知道页面长什么样子,我们还需要知道 DOM 的每一个节点的样式。主线程在解析页面时,遇到 style 标签或者 link 标签的 CSS 资源,会加载 CSS 代码,根据 CSS 代码确定每个 DOM 节点的计算样式(computed style)。
2,计算样式是主线程根据 CSS 样式选择器(CSS selectors)计算出的每个 DOM 元素应该具备的具体样式,即使你的页面没有设置任何自定义的样式,浏览器也会提供其默认的样式。
布局(Layout)
1,DOM 树和计算样式完成后,我们还需要知道每一个节点在页面上的位置,布局(Layout)其实就是找到所有元素的几何关系的过程。
2,主线程会遍历 DOM 及相关元素的计算样式,构建出包含每个元素的页面坐标信息及盒子模型大小的布局树(Render Tree),遍历过程中,会跳过隐藏的元素(display: none),另外,伪元素虽然在 DOM 上不可见,但是在布局树上是可见的。
绘制(Paint)
1,有了 DOM、样式和布局,仍然不足以渲染页面。还要解决先画什么后画什么,即绘制顺序的问题。比如,z-index 影响元素叠放,如果有这个属性,那简单地按元素在 HTML 中出现的顺序绘制就会出错。
2,布局 layout 之后,我们虽然知道了不同元素的结构,样式,几何关系,但要绘制出一个页面,我们还需要知道每个元素的绘制先后顺序,因此在绘制阶段,主线程会遍历布局树(layout tree),生成一系列的绘画记录(paint records)。绘画记录可以看做是记录各元素绘制先后顺序的笔记。比如“先画背景,然后画文本,最后画矩形”。
3,渲染是一个流水线作业(pipeline):前一道工序的输出就是下一道工序的输入。这意味着如果布局树有变化,则相应的绘制记录也要重新生成。
4,如果元素有动画,浏览器就需要每帧运行一次渲染流水线。目前显示器的刷新率为每秒 60 次(60fps),也就是说每秒更新 60 帧,动画会显得很流畅。如果中间缺了帧,那页面看起来就会“闪眼睛”。
5,即便渲染操作的频率能跟上屏幕刷新率,但由于计算发生在主线程上,而主线程可能因为运行 JavaScript 被阻塞。此时动画会因为阻塞被卡住。
- 此时,可以使用 requestAnimationFrame()将涉及动画的 JavaScript 操作分块并调度到每一帧的开始去运行。对于耗时的不必操作 DOM 的 JavaScript 操作,可以考虑 Web Worker,避免阻塞主线程。
合成(Compositing)
1,文档结构、元素的样式、元素的几何关系、绘画顺序,这些信息我们都有了,这个时候如果要绘制一个页面,我们需要做的是把这些信息转化为显示器中的像素,这个转化的过程,叫做光栅化(rasterizing)。
2,要绘制一个页面,最简单的做法是只光栅化视口内(viewport)的网页内容,如果用户进行了页面滚动,就移动光栅帧(rastered frame)并且光栅化更多的内容以补上页面缺失的部分,如下:
- Chrome 第一个版本就是采用这种简单的绘制方式,这一方式唯一的缺点就是每当页面滚动,光栅线程都需要对新移进视图的内容进行光栅化,这是一定的性能损耗,为了优化这种情况,Chrome 采取一种更加复杂的叫做合成(compositing)的做法。
3,什么是合成?合成是一种将页面分成若干层,然后分别对它们进行光栅化,最后在一个单独的线程 - 合成线程(compositor thread)里面合并成一个页面的技术。当用户滚动页面时,由于页面各个层都已经被光栅化了,浏览器需要做的只是合成一个新的帧来展示滚动后的效果罢了。页面的动画效果实现也是类似,将页面上的层进行移动并构建出一个新的帧即可。
4,为了实现合成技术,需要对元素进行分层,确定哪些元素需要放置在哪一层,主线程需要遍历渲染树来创建一棵层次树(Layer Tree),对于添加了 will-change CSS 属性的元素,会被看做单独的一层,没有 will-change CSS 属性的元素,浏览器会根据情况决定是否要把该元素放在单独的层。
5,一旦 Layer Tree 被创建,渲染顺序被确定,主线程会把这些信息通知给合成器线程,合成器线程开始对层次树的每一层进行光栅化。有的层可以达到整个页面的大小,所以合成线程需要将它们切分为一块又一块的小图块(tiles),之后将这些小图块分别进行发送给一系列光栅线程(raster threads)进行光栅化,结束后光栅线程会将每个图块的光栅结果存在 GPU Process 的内存中。
6,为了优化显示体验,合成线程可以给不同的光栅线程赋予不同的优先级,将那些在视口中的或者视口附近的层先被光栅化。
7,当图层上面的图块都被栅格化后,合成线程会收集图块上面叫做绘画四边形(draw quads)的信息来构建一个合成帧(compositor frame)。
绘画四边形:包含图块在内存的位置以及图层合成后图块在页面的位置之类的信息。
合成帧:代表页面一个帧的内容的绘制四边形集合。
8,以上所有步骤完成后,合成线程就会通过 IPC 向浏览器进程(browser process)提交(commit)一个渲染帧。这个时候可能有另外一个合成帧被浏览器进程的 UI 线程(UI thread)提交以改变浏览器的 UI。这些合成帧都会被发送给 GPU 从而展示在屏幕上。如果合成线程收到页面滚动的事件,合成线程会构建另外一个合成帧发送给 GPU 来更新页面。
9,合成的好处在于这个过程没有涉及到主线程,所以合成线程不需要等待样式的计算以及 JavaScript 完成执行。这就是为什么合成器相关的动画最流畅,如果某个动画涉及到布局或者绘制的调整,就会涉及到主线程的重新计算,自然会慢很多。
什么是光栅化:光栅化是将一个图元转变为一个二维图像的过程(就是将几何数据经过一系列变换后最终转换为像素,从而呈现在显示设备上的过程)。二维图像上每个点都包含了颜色、深度和纹理数据。将该点和相关信息叫做一个片元(fragment)。光栅化的目的,是找出一个几何单元(比如三角形)所覆盖的像素。
粗略地讲:模型的那些顶点在经过各种矩阵变换后也仅仅是顶点。而由顶点构成的三角形要在屏幕上显示出来,除了需要三个顶点的信息以外,还需要确定构成这个三角形的所有像素的信息。光栅化就是干这个的:
- 光栅化会根据三角形顶点的位置,来确定需要多少个像素点才能构成这个三角形,以及每个像素点都应该得到哪些信息,比如 uv 坐标该是什么等。这是通过对顶点数据进行插值来完成的。
- 光栅化是将几何数据经过一系列变换后最终转换为像素,从而呈现在显示设备上的过程,如下图:
光栅化的本质是坐标变换、几何离散化。
总结:光栅化,就是将几何信息转换成一个个栅格组成的图像的过程。
光栅化主要有以下几步:
读取模型的顶点,3 个 3 个的读,因为要画三角形。
将 3 个顶点两两连成线,形成三角形。
计算屏幕像素点在三角形内还是三角形外。在三角形内部的,就上色(颜色是通过一系列计算得出来的),在三角形外部的,就不上色。
注意:如果一个三角形挡在另一个三角形前面,我们应该只画前面的三角形。所以这里还需要比较一下正准备上色的这个像素点是不是已经上过色了。如果这个像素点已经上过色了,并且它是被 Z=1 的顶点上的色,而我们正准备上色的这个顶点的 Z=2(说明这个顶点被挡在了后面),那么这个顶点就不应该上色,因为它是被挡住的点。
不断的循环,直到画完 3D 模型的所有三角形,这样一个模型就出来了。
浏览器对事件的处理
浏览器对事件的处理说明
1,当页面渲染完毕以后,TAB 内已经显示出了可交互的 WEB 页面,用户可以进行移动鼠标、点击页面等操作了,而当这些事件发生时候,浏览器是如何处理这些事件的呢?
- 以点击事件(click event)为例,让鼠标点击页面时候,首先接受到事件信息的是 Browser Process,但是 Browser Process 只知道事件发生的类型和发生的位置,具体怎么对这个点击事件进行处理,还是由 Tab 内的 Renderer Process 进行的。Browser Process 接受到事件后,随后便把事件的信息传递给了渲染进程,渲染进程会找到根据事件发生的坐标,找到目标对象(target),并且运行这个目标对象的点击事件绑定的监听函数(listener)。
渲染进程中合成器线程接收事件
1,前面我们说到,合成器线程可以独立于主线程之外通过已光栅化的层创建组合帧,例如页面滚动,如果没有对页面滚动绑定相关的事件,组合器线程可以独立于主线程创建组合帧,如果页面绑定了页面滚动事件,合成器线程会等待主线程进行事件处理后才会创建组合帧。那么,合成器线程是如何判断出这个事件是否需要给主线程处理的呢?
- 由于执行 JS 是主线程的工作,当页面合成时,合成器线程会标记页面中绑定有事件处理器的区域为非快速滚动区域(non-fast scrollable region),如果事件发生在这些存在标注的区域,合成器线程会把事件信息发送给主线程,等待主线程进行事件处理,如果事件不是发生在这些区域,合成器线程则会直接合成新的帧而不用等到主线程的响应。
2,而对于非快速滚动区域的标记,开发者需要注意全局事件的绑定,比如我们使用事件委托,将目标元素的事件交给根元素 body 进行处理,代码如下:
1 | document.body.addEventListener("touchstart", (event) => { |
- 在开发者角度看,这一段代码没什么问题,但是从浏览器角度看,这一段代码给 body 元素绑定了事件监听器,也就意味着整个页面都被编辑为一个非快速滚动区域,这会使得即使你的页面的某些区域没有绑定任何事件,每次用户触发事件时,合成器线程也需要和主线程通信并等待反馈,流畅的合成器独立处理合成帧的模式就失效了。
- 其实这种情况也很好处理,只需要在事件监听时传递 passtive 参数为 true,passtive 会告诉浏览器你既要绑定事件,又要让组合器线程直接跳过主线程的事件处理直接合成创建组合帧。
1 | document.body.addEventListener( |
查找事件的目标对象(event target)
1,当合成器线程接收到事件信息,判定到事件发生不在非快速滚动区域后,合成器线程会向主线程发送这个事件信息,主线程获取到事件信息的第一件事就是通过命中测试(hit test)去找到事件的目标对象。具体的命中测试流程是遍历在绘制阶段生成的绘画记录(paint records)来找到包含了事件发生坐标上的元素对象。
浏览器对事件的优化
1,一般我们屏幕的帧率是每秒 60 帧,也就是 60fps,但是某些事件触发的频率超过了这个数值,比如 wheel,mousewheel,mousemove,pointermove,touchmove,这些连续性的事件一般每秒会触发 60~120 次,假如每一次触发事件都将事件发送到主线程处理,由于屏幕的刷新速率相对来说较低,这样使得主线程会触发过量的命中测试以及 JS 代码,使得性能有了没必要是损耗。
- 出于优化的目的,浏览器会合并这些连续的事件,延迟到下一帧渲染是执行,也就是 requestAnimationFrame 之前。
- 而对于非连续性的事件,如 keydown,keyup,mousedown,mouseup,touchstart,touchend 等,会直接派发给主线程去执行。
总结
1,浏览器的多进程架构,根据不同的功能划分了不同的进程,进程内不同的使命划分了不同的线程,当用户开始浏览网页时候,浏览器进程进行处理输入、开始导航请求数据、请求响应数据,查找新建渲染进程,提交导航,之后渲染又进行了解析 HTML 构建 DOM、构建过程加载子资源、下载并执行 JS 代码、样式计算、布局、绘制、合成,一步一步的构建出一个可交互的 WEB 页面,之后浏览器进程又接受页面的交互事件信息,并将其交给渲染进程,渲染进程内主进程进行命中测试,查找目标元素并执行绑定的事件,完成页面的交互。
发布时间: 2021-05-30
最后更新: 2022-04-17
本文标题: Browser
本文链接: https://dnhyxc.gitee.io/2021/05/30/browser/
版权声明: 本作品采用 CC BY-NC-SA 4.0 许可协议进行许可。转载请注明出处!