浏览器如何解析网页
Posted 张驰Terry
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浏览器如何解析网页相关的知识,希望对你有一定的参考价值。
一旦浏览器收到数据的第一块,它就可以开始解析收到的信息。“推测性解析”,“解析”是浏览器将通过网络接收的数据转换为DOM和CSSOM的步骤,通过渲染器把DOM和CSSOM在屏幕上绘制成页面。
DOM是浏览器标记的内部表示。DOM也是被暴露的,可以通过javascript中的各种API进行DOM操作。
即使请求页面的html大于初始的14KB数据包,浏览器也将开始解析并尝试根据其拥有的数据进行渲染。这就是为什么在前14Kb中包含浏览器开始渲染页面所需的所有内容,或者至少包含页面模板(第一次渲染所需的CSS和HTML)对于web性能优化来说是重要的。但是在渲染到屏幕上面之前,HTML、CSS、JavaScript必须被解析完成。
构建DOM树
我们描述五个步骤在这篇文章中 critical rendering path.
第一步是处理HTML标记并构造DOM树。HTML解析涉及到 tokenization 和树的构造。HTML标记包括开始和结束标记,以及属性名和值。 如果文档格式良好,则解析它会简单而快速。解析器将标记化的输入解析到文档中,构建文档树。
DOM树描述了文档的内容。<html>元素是第一个标签也是文档树的根节点。树反映了不同标记之间的关系和层次结构。嵌套在其他标记中的标记是子节点。DOM节点的数量越多,构建DOM树所需的时间就越长。
当解析器发现非阻塞资源,例如一张图片,浏览器会请求这些资源并且继续解析。当遇到一个CSS文件时,解析也可以继续进行,但是对于<script>标签(特别是没有 [async](<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function>)
或者 defer
属性)会阻塞渲染并停止HTML的解析。尽管浏览器的预加载扫描器加速了这个过程,但过多的脚本仍然是一个重要的瓶颈。
预加载扫描器
浏览器构建DOM树时,这个过程占用了主线程。当这种情况发生时,预加载扫描仪将解析可用的内容并请求高优先级资源,如CSS、JavaScript和web字体。多亏了预加载扫描器,我们不必等到解析器找到对外部资源的引用来请求它。它将在后台检索资源,以便在主HTML解析器到达请求的资源时,它们可能已经在运行,或者已经被下载。预加载扫描仪提供的优化减少了阻塞。
<link rel="stylesheet" src="styles.css"/><script src="myscript.js" async></script><img src="myimage.jpg" alt="image description"/><script src="anotherscript.js" async></script>
Copy to Clipboard
在这个例子中,当主线程在解析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树包括来自用户代理样式表的样式。浏览器从适用于节点的最通用规则开始,并通过应用更具体的规则递归地优化计算的样式。换句话说,它级联属性值。
构建CSSOM非常非常快,并且在当前的开发工具中没有以独特的颜色显示。相反,开发人员工具中的“重新计算样式”显示解析CSS、构造CSSOM树和递归计算计算样式所需的总时间。在web性能优化方面,它是可轻易实现的,因为创建CSSOM的总时间通常小于一次DNS查找所需的时间。
其他过程
JavaScript 编译
当CSS被解析并创建CSSOM时,其他资源,包括JavaScript文件正在下载(多亏了preload scanner)。JavaScript被解释、编译、解析和执行。脚本被解析为抽象语法树。一些浏览器引擎使用”Abstract Syntax Tree“并将其传递到解释器中,输出在主线程上执行的字节码。这就是所谓的JavaScript编译。
构建辅助功能树
浏览器还构建辅助设备用于分析和解释内容的辅助功能(accessibility )树。可访问性对象模型(AOM)类似于DOM的语义版本。当DOM更新时,浏览器会更新辅助功能树。辅助技术本身无法修改可访问性树。
在构建AOM之前,屏幕阅读器(screen readers)无法访问内容。
渲染
渲染步骤包括样式、布局、绘制,在某些情况下还包括合成。在解析步骤中创建的CSSOM树和DOM树组合成一个Render树,然后用于计算每个可见元素的布局,然后将其绘制到屏幕上。在某些情况下,可以将内容提升到它们自己的层并进行合成,通过在GPU而不是CPU上绘制屏幕的一部分来提高性能,从而释放主线程。
Style
第三步是将DOM和CSSOM组合成一个Render树,计算样式树或渲染树从DOM树的根开始构建,遍历每个可见节点。
像<head>和它的子节点以及任何具有display: none
样式的结点,例如script { display: none; }
(在user agent stylesheets可以看到这个样式)这些标签将不会显示,也就是它们不会出现在Render树上。具有visibility: hidden
的节点会出现在Render树上,因为它们会占用空间。由于我们没有给出任何指令来覆盖用户代理默认值,因此上面代码示例中的script节点将不会包含在Render树中。
每个可见节点都应用了其CSSOM规则。Render树保存所有具有内容和计算样式的可见节点——将所有相关样式匹配到DOM树中的每个可见节点,并根据CSS级联确定每个节点的计算样式。
Layout
第四步是在渲染树上运行布局以计算每个节点的几何体。布局是确定呈现树中所有节点的宽度、高度和位置,以及确定页面上每个对象的大小和位置的过程。回流是对页面的任何部分或整个文档的任何后续大小和位置的确定。
构建渲染树后,开始布局。渲染树标识显示哪些节点(即使不可见)及其计算样式,但不标识每个节点的尺寸或位置。为了确定每个对象的确切大小和位置,浏览器从渲染树的根开始遍历它。
在网页上,大多数东西都是一个盒子。不同的设备和不同的桌面意味着无限数量的不同的视区大小。在此阶段,考虑到视区大小,浏览器将确定屏幕上所有不同框的尺寸。以视区的大小为基础,布局通常从body开始,用每个元素的框模型属性排列所有body的子孙元素的尺寸,为不知道其尺寸的替换元素(例如图像)提供占位符空间。
第一次确定节点的大小和位置称为布局。随后对节点大小和位置的重新计算称为回流。在我们的示例中,假设初始布局发生在返回图像之前。由于我们没有声明图像的大小,因此一旦知道图像大小,就会有回流。
Paint
最后一步是将各个节点绘制到屏幕上,第一次出现的节点称为first meaningful paint。在绘制或光栅化阶段,浏览器将在布局阶段计算的每个框转换为屏幕上的实际像素。绘画包括将元素的每个可视部分绘制到屏幕上,包括文本、颜色、边框、阴影和替换的元素(如按钮和图像)。浏览器需要非常快地完成这项工作。
为了确保平滑滚动和动画,占据主线程的所有内容,包括计算样式,以及回流和绘制,必须让浏览器在16.67毫秒内完成。在2048x 1536,iPad有超过314.5万像素将被绘制到屏幕上。那是很多像素需要快速绘制。为了确保重绘的速度比初始绘制的速度更快,屏幕上的绘图通常被分解成数层。如果发生这种情况,则需要进行合成。
绘制可以将布局树中的元素分解为多个层。将内容提升到GPU上的层(而不是CPU上的主线程)可以提高绘制和重新绘制性能。有一些特定的属性和元素可以实例化一个层,包括<video>和<canvas>,任何CSS属性为opacity、3D转换、[will-change](<https://developer.mozilla.org/en-US/docs/Web/CSS/will-change>)
的元素,还有一些其他元素。这些节点将与子节点一起绘制到它们自己的层上,除非子节点由于上述一个(或多个)原因需要自己的层。
层确实可以提高性能,但是它以内存管理为代价,因此不应作为web性能优化策略的一部分过度使用。
Compositing
当文档的各个部分以不同的层绘制,相互重叠时,必须进行合成,以确保它们以正确的顺序绘制到屏幕上,并正确显示内容。
当页面继续加载资产时,可能会发生回流(回想一下我们迟到的示例图像),回流会触发重新绘制和重新组合。如果我们定义了图像的大小,就不需要重新绘制,只需要重新绘制需要重新绘制的层,并在必要时进行合成。但我们没有包括图像大小!从服务器获取图像后,渲染过程将返回到布局步骤并从那里重新开始。
交互
一旦主线程绘制页面完成,你会认为我们已经“准备好了”,但事实并非如此。如果加载包含JavaScript(并且延迟到[onload](<https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onload>)
事件激发后执行),则主线程可能很忙,无法用于滚动、触摸和其他交互。
”Time to Interactive“(TTI)是测量从第一个请求导致DNS查找和SSL连接到页面可交互时所用的时间——可交互是”First Contentful Paint“之后的时间点,页面在50ms内响应用户的交互。如果主线程正在解析、编译和执行JavaScript,则它不可用,因此无法及时(小于50ms)响应用户交互。
在我们的示例中,可能图像加载很快,但anotherscript.js
文件可能是2 MB,而且用户的网络连接很慢。在这种情况下,用户可以非常快地看到页面,但是在下载、解析和执行脚本之前,就无法滚动。这不是一个好的用户体验。避免占用主线程,如下面的WebPageTest示例所示:
在本例中,DOM内容加载过程花费了1.5秒多的时间,主线程在这段时间内完全被占用,对单击事件或屏幕点击没有响应。
以上是关于浏览器如何解析网页的主要内容,如果未能解决你的问题,请参考以下文章