文章内容来源于MDN 渲染页面:浏览器的工作原理
不知道从什么时候起,前端面试开始流行问:“浏览器从URL输入到页面呈现到底发生了什么? ”,这就是在变相的问浏览器的工作原理,所以想答好这个题目就必须要了解浏览器的工作原理。浏览器从URL输入到页面呈现大体要经过导航、响应、解析、渲染、交互五个步骤,以下就从这五个步骤分别介绍浏览器都做了什么工作。
1、导航
导航是加载web页面的第一步。它发生在以下情形:用户通过在地址栏输入一个URL、点击一个链接、提交表单或者是其他的行为。
Web性能优化的目标之一就是缩短导航完成所花费的时间,在理想情况下,它通常不会花费太多的时间,但是网络延迟和带宽会让它变久。
DNS查询
对于一个web页面来说导航的第一步是要去寻找页面资源的位置。如果导航到 https://example.com,HTML页面被定位到IP地址为 93.184.216.34 的服务器。如果以前没有访问过这个网站,就需要进行 DNS查询。
浏览器向域名服务器发起DNS查询请求,最终得到一个IP地址。第一次请求之后,这个IP地址可能会被缓存一段时间,这样可以通过从缓存里面检索IP地址而不是再通过域名服务器进行查询来加速后续的请求。
每个主机名 (hostname) 在页面加载时通常只需要进行一次DNS查询。但是,对于页面指向的不同的主机名,则需要多次DNS查询。如果字体(font)、图像(image)、脚本(script)、广告(ads)和网站统计(metric)都有不同的主机名,则需要对每一个主机名进行DNS查询。
但是对于移动网络,DNS查询可能存在性能问题。当一个用户使用移动网络时,所有DNS查询必须从手机发送到基站,然后到达一个权威DNS服务器。手机、信号塔、域名服务器之间的距离会显著增加延迟。
TCP握手
一旦获取到服务器IP地址,浏览器就会通过TCP“三次握手”与服务器建立连接。这个机制是用来让两端尝试进行通信——在浏览器和服务器通过上层协议 HTTPS 发送数据之前,可以协商网络TCP套接字连接的一些参数。
TCP 的“三次握手”技术经常被称为“SYN-SYN-ACK”——更确切的说是SYN、SYN-ACK、ACK——因为通过TCP首先发送了三个消息进行协商,然后在两台电脑之间开始一个TCP会话。是的,这意味着当请求尚未发出的时候,终端与每台服务器之间还要来回多发送三条消息。
TLS协商
对于通过HTTPS建立的安全连接,还需要另一次 "握手"。这种握手,或者说 TLS 协商,决定使用哪种密码对通信进行加密,验证服务器,并在开始实际数据传输前建立安全连接。这就需要在实际发送内容请求之前,再往返服务器五次。
虽然建立安全连接的步骤增加了等待加载页面的时间,但是为了建立一个安全的连接而增加延迟是值得的,因为在浏览器和web服务器之间传输的数据不可以被第三方解密。
如此经过 8 次往返,浏览器终于可以发出请求。
2、响应
一旦我们建立了和web服务器的连接,浏览器就会代表用户发送一个初始的HTTP GET 请求,对于网站来说,这个请求通常是一个HTML文件。一旦服务器收到请求,它将使用相关的响应头和HTML的内容进行回复。
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>简单的页面</title>
<link rel="stylesheet" href="styles.css" />
<script src="myscript.js"></script>
</head>
<body>
<h1 class="heading">我的页面</h1>
<p>含有<a href="https://example.com/about">链接</a>的段落。</p>
<div>
<img src="myimage.jpg" alt="图像描述" />
</div>
<script src="anotherscript.js"></script>
</body>
</html>
初始请求的响应包含所接收数据的第一个字节。首字节时间(TTFB)是用户通过点击链接进行请求与收到第一个 HTML 数据包之间的时间。第一个内容分块通常是 14KB 的数据。
上面的示例中,这个请求肯定是小于 14KB 的,但是直到浏览器在解析阶段遇到链接时才会去请求链接的资源,下面有进行描述。
3、解析
一旦浏览器收到第一个数据分块,它就可以开始解析收到的信息。“解析”是浏览器将通过网络接收的数据(HTML)转换为DOM和CSSOM的步骤, 通过渲染器在屏幕上将它们绘制成页面。
虽然DOM是浏览器标记的内部表示,但是它也被暴露出来,可以通过JavaScript中的各种API进行操作。
即使请求页面的HTML大于初始的14KB数据包,浏览器也将根据其拥有的数据开始解析并尝试渲染。这就是为什么在前14KB中包含浏览器开始渲染页面所需的所有内容,或者至少包含页面模板(第一次渲染所需的 CSS和HTML)对于web性能优化来说是重要的。但是在绘制到屏幕上面之前,HTML、CSS、JavaScript 必须被解析完成。
提示:浏览器并不是在完全下载完HTML才开始解析的,它是基于流式数据边下载边编解析的。这也浏览器是为性能优化做出的努力。
构建 DOM 树
第一步是处理HTML标记并构造DOM树。HTML解析涉及到符号化和树的构造。HTML标记包括开始和结束标记,以及属性名和值。如果文档格式良好,则解析它会简单而快速。解析器将标记化的输入解析到文档中,构建文档树。
DOM树描述了文档的内容。<html>元素是第一个标签也是文档树的根节点。树反映了不同标记之间的关系和层次结构。嵌套在其他标记中的标记是子节点。DOM 节点的数量越多,构建 DOM 树所需的时间就越长。
当解析器发现非阻塞资源,例如一张图片,浏览器会请求这些资源并且继续解析。当遇到一个CSS文件时,解析也可以继续进行,但是对于 <script> 标签(特别是没有 async 或者 defer 属性的)会阻塞渲染并停止 HTML 的解析。尽管浏览器的预加载扫描器加速了这个过程,但过多的脚本仍然是一个重要的性能瓶颈。
提示:浏览器解析过程中加载的资源可分为:阻塞性资源和非阻塞性资源。阻塞性资源又可分为:呈现阻塞资源和解析器阻塞资源,呈现阻塞资源即CSS文件,解析器阻塞资源即JS文件。
预加载扫描器
预加载扫描器是浏览器对性能优化做出的另一努力,浏览器构建DOM树时,这个过程占用了主线程。同时,预加载扫描器会解析可用的内容并请求高优先级的资源,如CSS、JavaScript 和 web 字体。多亏了预加载扫描器,我们不必等到解析器找到对外部资源的引用时才去请求。它将在后台检索资源,而当主HTML解析器解析到要请求的资源时,它们可能已经下载中了,或者已经被下载。预加载扫描器提供的优化减少了阻塞。
<link rel="stylesheet" href="styles.css" />
<script src="myscript.js" async></script>
<img src="myimage.jpg" alt="图像描述" />
<script src="anotherscript.js" async></script>
在这个例子中,当主线程在解析HTML和CSS时,预加载扫描器将找到脚本和图像,并开始下载它们。为了确保脚本不会阻塞进程,当JavaScript解析和执行顺序不重要时,可以添加 async 属性或 defer 属性。
等待获取CSS不会阻塞HTML的解析或者下载,但是它确实会阻塞JavaScript,因为JavaScript经常用于查询元素的CSS属性。
构建 CSSOM 树
第二步是处理CSS并构建CSSOM树。CSS对象模型和DOM是相似的。DOM和CSSOM是两棵树。它们是独立的数据结构。浏览器将CSS规则转换为可以理解和使用的样式映射。浏览器遍历CSS中的每个规则集,根据CSS 选择器创建具有父、子和兄弟关系的节点树。
与HTML类似,浏览器需要将接收到的CSS规则转换为可处理的格式。因此,它重复了HTML到对象的过程,但这次是针对CSS。
CSSOM树包括来自用户代理(浏览器)样式表的样式。浏览器从适用于节点的最通用规则开始,并通过应用更具体的规则递归地优化计算的样式。换句话说,它级联属性值。
其他过程
JavaScript编译
在解析CSS和创建CSSOM的同时,包括JavaScript文件在内的其他资源也在下载(这要归功于预加载扫描器)。JavaScript会被解析、编译和解释。脚本被解析为抽象语法树。有些浏览器引擎会将抽象语法树输入编译器,输出字节码。这就是所谓的JavaScript编译。大部分代码都是在主线程上解释的,但也有例外,例如在web worker中运行的代码。
构建无障碍树
浏览器还构建辅助设备用于分析和解释内容的无障碍树。无障碍对象模型(AOM)类似于DOM的语义版本。当DOM更新时,浏览器会更新辅助功能树。辅助技术本身无法修改无障碍树。在构建AOM之前,屏幕阅读器无法访问内容。
4、渲染
渲染步骤包括样式、布局、绘制,在某些情况下还包括合成。在解析步骤中创建的CSSOM树和DOM树组合成一个渲染树,然后用于计算每个可见元素的布局,然后将其绘制到屏幕上。在某些情况下,可以将内容提升到它们自己的层并进行合成,通过在GPU而不是CPU上绘制屏幕的一部分来提高性能,从而释放主线程。
样式
关键渲染路径的第三步是将DOM和CSSOM组合成渲染树。计算样式树或渲染树的构建从DOM树的根开始,遍历每个可见节点。
不会被显示的元素,如<head>元素及其子元素,以及任何带有display: none的节点,如用户代理样式表中的 script { display: none; },都不会包含在渲染树中,因为它们不会出现在渲染输出中。应用了 visibility: hidden 的节点会包含在渲染树中,因为它们会占用空间。由于我们没有给出任何指令来覆盖用户代理默认值,因此上述代码示例中的 script 节点不会包含在渲染树中。
每个可见节点都应用了CSSOM规则。渲染树包含所有可见节点的内容和计算样式,将所有相关样式与DOM树中的每个可见节点匹配起来,并根据CSS级联,确定每个节点的计算样式。
布局
第四步是在渲染树上运行布局以计算每个节点的几何体。布局是确定呈现树中所有节点的尺寸和位置,以及确定页面上每个对象的大小和位置的过程。重排是后续过程中对页面的任意部分或整个文档的大小和位置的重新计算。
渲染树构建完毕后,浏览器就开始布局。渲染树标识了哪些节点会显示(即使不可见)及其计算样式,但不标识每个节点的尺寸或位置。为了确定每个对象的确切大小和位置,浏览器会从渲染树的根开始遍历。
在网页上,大多数东西都是一个盒子。不同的设备和不同的桌面设置意味着无限数量的不同视区大小。在此阶段,根据视口大小,浏览器将确定屏幕上所有盒子的大小。以视口大小为基础,布局通常从 body 开始,设置所有body后代的大小,同时给不知道其尺寸的替换元素(例如图像)提供占位符空间,空间大小以相应元素盒模型的属性为准。
第一次确定每个节点的大小和位置称为布局。随后对节点大小和位置的重新计算称为重排。 在我们的示例中,假设初始布局发生在返回图像之前。由于我们没有声明图像的尺寸,因此一旦知道图像的尺寸,就会出现重排。
绘制
关键渲染路径中的最后一步是将各个节点绘制到屏幕上,其中第一次的绘制被称为首次有意义的绘制即FMP。在绘制或光栅化阶段,浏览器将在布局阶段计算的每个盒子转换为屏幕上的实际像素。绘制涉及将元素的每个可见部分绘制到屏幕上,包括文本、颜色、边框、阴影以及按钮和图像等替换元素。浏览器需要以超快的速度执行这个过程。
为了确保平滑滚动和动画效果,包括计算样式、回流和绘制等占用主线程的所有操作,必须在不超过 16.67 毫秒的时间内完成。 在 2048 x 1536 分辨率下,iPad 需要将超过 314.5 万个像素绘制到屏幕上。这是非常多的像素,必须要非常快速地绘制出来。为了确保重绘能够比初始绘制更快地完成,绘制到屏幕的操作通常被分解成几个图层。如果发生这种情况,浏览器则需要进行合成。
绘制可以将布局树中的元素分解为多个层。将内容提升到 GPU 上的层(而不是 CPU 上的主线程)可以提高绘制和重新绘制性能。有一些特定的属性和元素可以实例化一个层,包括 <video> 和 <canvas>,任何 CSS 属性为 opacity 、3D transform、will-change 的元素,还有一些其他元素。这些节点将与子节点一起绘制到它们自己的层上,除非子节点由于上述一个(或多个)原因需要自己的层。
分层确实可以提高性能,但在内存管理方面成本较高,因此不应作为 Web 性能优化策略的过度使用。
合成
当文档的各个部分以不同的层绘制,相互重叠时,必须进行合成,以确保它们以正确的顺序绘制到屏幕上,并正确显示内容。
当页面继续加载资源时,可能会发生回流(回想一下我们迟到的示例图像),回流会触发重新绘制和重新合成。如果我们定义了图像的大小,就不需要重新绘制,只需要绘制需要重新绘制的层,并在必要时进行合成。但我们并没有定义图像大小!所以从服务器获取图像后,渲染过程将返回到布局步骤并从那里重新开始。
5、交互
一旦主线程绘制页面完成,你会认为我们已经“准备好了”,但事实并非如此。如果加载包括正确延迟加载的JavaScript,并且仅在onload事件触发后执行,那么主线程可能会忙于执行脚本,无法用于滚动、触摸和其他交互操作。
可交互时间(TTI)是测量从第一个请求导致DNS查询和SSL连接到页面可交互时所用的时间——可交互是在首次内容绘制之后页面在 50ms 内响应用户的交互。如果主线程正在解析、编译和执行 JavaScript,则无法及时(小于 50ms)响应用户交互。
在我们的示例中,可能图像加载很快,但 anotherscript.js 文件的大小可能是 2MB,而且用户的网络连接很慢。在这种情况下,用户可以非常快地看到页面,但是在下载、解析和执行脚本之前,就无法滚动。这不是一个好的用户体验。避免占用主线程,如下面的网页测试示例所示:
在本例中,DOM 内容加载过程花费了超过 1.5 秒的时间,主线程在这段时间内完全被占用,对单击事件或屏幕点击没有响应。
总结
-
浏览器从URL输入到页面呈现要经过导航→响应→解析→渲染→交互五个步骤。
-
导航→响应过程负责请求DNS查询获取IP地址后通过TCP握手及TLS协商和服务器建立连接,然后通过HTTPS协议开始两端通信。
-
解析→渲染过程负责构建DOM树和CSSOM树,在渲染阶段合并DOM树、CSSOM树为渲染树,根据渲染树计算样式和布局然后把节点绘制到屏幕上,此次过程也被称为关键渲染路径。
-
交互过程负责响应用户输入的事件,如果JavaScript的解析和执行太长会无法及时的响应用户。
-
导致Web性能问题的原因主要有两种,一是网络延迟(网络性能),二是大部分情况下的浏览器单线程执行(渲染性能)。
在了解了浏览器的工作原理之后,接下来咱们学习下衡量Web性能的指标及其具体表达的含义,看看它是如何同浏览器的工作过程对应起来的。