浏览器的工作原理
https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/#The_browser_high_level_structure
浏览器简介
浏览器的主要功能
- 向服务器发起请求
- 在浏览器窗口中展示返回的资源(HTML文档、PDF、图片等)
浏览器的结构
浏览器主要包括用户界面User Interface、浏览器引擎Browser engine、呈现引擎Rendering engine、网络Networking、用户界面后端UI Backend、JavaScript解释器JavaScript Interpreter、数据存储Data Persistence。
- 用户界面User Interface。包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的请求的页面外,其他显示的各个部分都属于用户界面。
- 浏览器引擎Browser engine。在用户界面和呈现引擎之间传送指令。
- 呈现引擎Rendering engine。负责显示请求的内容。如果请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。
- 网络Networking。用于网络调用,比如 HTTP 请求。
- 用户界面后端UI Backend。用于绘制基本的窗口小部件,比如组合框和窗口。在底层使用操作系统的用户界面方法。
- JavaScript解释器JavaScript Interpreter。用于解析和执行 JavaScript 代码。
- 数据存储Data Persistence。这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“网络数据库”,这是一个完整(但是轻便)的浏览器内数据库。
Chrome 浏览器的每个标签页都分别对应一个呈现引擎实例。每个标签页都是一个独立的进程。
呈现引擎
默认情况下,呈现引擎可显示 HTML 和 XML 文档与图片。通过插件(或浏览器扩展程序),还可以显示其他类型的内容;例如,使用 PDF 查看器插件就能显示 PDF 文档。
Firefox 使用的呈现引擎是 Gecko。而 Safari 和 Chrome 浏览器的呈现引擎使用的都是 WebKit。
呈现引擎采用了单线程。几乎所有操作(除了网络操作)都是在单线程中进行的。在 Chrome 浏览器中,该线程是标签进程的主线程。网络操作可由多个并行线程执行。并行连接数是有限的,通常为 2 至 6 个。
呈现引擎的工作主流程
呈现引擎一开始会从网络层获取请求文档的内容,内容的大小是有限制的。
- 然后开始解析 HTML 文档,并将各标记逐个转化成“内容树”上的DOM 节点。
- 同时也会解析外部 CSS 文件以及样式元素中的样式数据。HTML 中这些带有视觉指令的样式信息将用于创建另一个树结构:呈现树。(webkit:连接 DOM 节点和可视化信息从而创建呈现树)
- 呈现树构建完毕之后,进入“布局”处理阶段,也就是为每个节点分配一个应出现在屏幕上的确切坐标。
- 绘制 - 呈现引擎会遍历呈现树,由用户界面后端层将每个节点绘制出来。
webkit主流程见下方
注意:
为达到更好的用户体验,呈现引擎会力求尽快将内容显示在屏幕上。它不必等到整个 HTML 文档解析完毕之后,就会开始构建呈现树和设置布局。在不断接收和处理来自网络的其余内容的同时,呈现引擎会将部分内容解析并显示出来。
解析是什么?解析文档是指将文档转化成为有意义的结构,也就是可让代码理解和使用的结构。
解析得到的结果通常是代表了文档结构的节点树,它称作解析树或者语法树。
解析的过程可以分成两个子过程:词法分析和语法分析。
词法分析是将输入内容分割成大量标记的过程。语法分析是应用语言的语法规则的过程。
HTML解析器
HTML 解析器的任务是将 HTML 标记解析成解析树。
HTML 无法用常规的自上而下或自下而上的解析器进行解析。原因在于:
- 语言的宽容。浏览器历来对一些常见的无效 HTML 用法采取包容态度。
- 解析过程需要不断地反复。源内容在解析过程中通常不会改变,但是在 HTML 中,脚本标记如果包含
document.write
,就会添加额外的标记,这样解析过程实际上就更改了输入内容。
HTML解析算法:http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html
HTML 解析算法由两个阶段组成:标记化和树构建。
标记化
标记化是词法分析过程,将输入内容解析成多个标记。HTML 标记包括起始标记、结束标记、属性名称和属性值。标记生成器识别标记,传递给树构造器,然后接受下一个字符以识别下一个标记;如此反复直到输入的结束。
树构建算法
在创建解析器的同时,也会创建 Document 对象。
树构建阶段,以 Document 为根节点的 DOM 树也会不断进行修改,向其中添加各种元素。
解析完成之后,浏览器会将文档标注为交互状态,并开始解析那些处于“deferred”模式的脚本,也就是那些应在文档解析完成后才执行的脚本。然后,文档状态将设置为“完成”,一个“加载”事件将随之触发。
CSS解析
CSS 是上下文无关的语法,可以使用简介中描述的各种解析器进行解析。
css的词法和语法:http://www.w3.org/TR/CSS2/grammar.html
处理脚本样式和顺序
脚本
解析器遇到 <script>
标记时立即解析并执行脚本。文档的解析将停止,直到脚本执行完毕。如果脚本是外部的,那么解析过程会停止,直到从网络同步抓取资源完成后再继续。
将脚本标注为“defer”,这样它就不会停止文档解析,而是等到解析结束才执行。
HTML5 增加了一个选项,可将脚本标记为异步,以便由其他线程解析和执行。
预解析
在执行脚本时,其他线程会解析文档的其余部分,找出并加载需要通过网络加载的其他资源。
通过这种方式,资源可以在并行连接上加载,从而提高总体速度。
预解析器不会修改 DOM 树,而是将这项工作交由主解析器处理;预解析器只会解析外部资源(例如外部脚本、样式表和图片)的引用。
样式表
理论上来说,应用样式表不会更改 DOM 树,因此似乎没有必要等待样式表并停止文档解析。
但这涉及到一个问题,就是脚本在文档解析阶段会请求样式信息。如果当时还没有加载和解析样式,脚本就会获得错误的回复,这样显然会产生很多问题。
对于 WebKit 而言,仅当脚本尝试访问的样式属性可能受尚未加载的样式表影响时,它才会禁止该脚本。
呈现树构建
在 DOM 树构建的同时,浏览器还会构建另一个树结构:呈现树。
这是由可视化元素按照其显示顺序而组成的树,也是文档的可视化表示。
它的作用是让您按照正确的顺序绘制内容。
呈现树和DOM树的关系
1、呈现器是和 DOM 元素相对应的,但并非一一对应。
2、非可视化的 DOM 元素不会插入呈现树中,例如“head”元素。
3、如果元素的 display 属性值为“none”,那么也不会显示在呈现树中(但是 visibility 属性值为“hidden”的元素仍会显示)。
4、有一些 DOM 元素对应多个可视化对象。它们往往是具有复杂结构的元素,无法用单一的矩形来描述。例如,“select”元素有 3 个呈现器:一个用于显示区域,一个用于下拉列表框,还有一个用于按钮。如果由于宽度不够,文本无法在一行中显示而分为多行,那么新的行也会作为新的呈现器而添加。
5、另一个关于多呈现器的例子是格式无效的 HTML。根据 CSS 规范,inline 元素只能包含 block 元素或 inline 元素中的一种。如果出现了混合内容,则应创建匿名的 block 呈现器,以包裹 inline 元素。
6、有一些呈现对象对应于 DOM 节点,但在树中所在的位置与 DOM 节点不同。浮动定位和绝对定位的元素就是这样,它们处于正常的流程之外,放置在树中的其他地方,并映射到真正的框架,而放在原位的是占位框架。
构建呈现树的流程
处理 html 和 body 标记就会构建呈现树根节点。这个根节点呈现对象对应于 CSS 规范中所说的容器 block,这是最上层的 block,包含了其他所有 block。它的尺寸就是视口,即浏览器窗口显示区域的尺寸。
样式计算
样式表的来源包括浏览器的默认样式表、由网页作者提供的样式表以及由浏览器用户提供的用户样式表
布局
呈现器在创建完成并添加到呈现树时,并不包含位置和大小信息。计算这些值的过程称为布局或重排。
Dirty位系统
为避免对所有细小更改都进行整体布局,浏览器采用了一种“dirty 位”系统。如果某个呈现器发生了更改,或者将自身及其子代标注为“dirty”,则需要进行布局。
有两种标记:“dirty”和“children are dirty”。“children are dirty”表示尽管呈现器自身没有变化,但它至少有一个子代需要布局。
全局布局和增量布局
全局布局是指触发了整个呈现树范围的布局,触发原因可能包括:
1、影响所有呈现器的全局样式更改,例如字体大小更改
2、屏幕大小调整
布局可以采用增量方式,也就是只对 dirty 呈现器进行布局(这样可能存在需要进行额外布局的弊端)。
当呈现器为 dirty 时,会异步触发增量布局。例如,当来自网络的额外内容添加到 DOM 树之后,新的呈现器附加到了呈现树中
绘制
https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/#stackingcontext
块呈现器的堆栈顺序如下:
- 背景颜色
- 背景图片
- 边框
- 子代
- 轮廓
在重新绘制之前,WebKit 会将原来的矩形另存为一张位图,然后只绘制新旧矩形之间的差异部分。
在发生变化时,浏览器会尽可能做出最小的响应。因此,元素的颜色改变后,只会对该元素进行重绘。元素的位置改变后,只会对该元素及其子元素(可能还有同级元素)进行布局和重绘。添加 DOM 节点后,会对该节点进行布局和重绘。一些重大变化(例如增大“html”元素的字体)会导致缓存无效,使得整个呈现树都会进行重新布局和绘制
事件循环
浏览器的主线程是事件循环。它是一个无限循环,永远处于接受处理状态,并等待事件(如布局和绘制事件)发生,并进行处理。
CSS2可视化模型
画布
指“用来呈现格式化结构的空间”,也就是供浏览器绘制内容的区域。画布的空间尺寸大小是无限的,但是浏览器会根据视口的尺寸选择一个初始宽度。画布如果包含在其他画布内,就是透明的;否则会由浏览器指定一种颜色。
css框模型
针对文档树中的元素而生成,并根据可视化格式模型进行布局的矩形框。每个框都有一个内容区域(例如文本、图片等),还有可选的周围补白、边框和边距区域。
所有元素都有一个“display”属性,决定了它们所对应生成的框类型(block、none、inline-block、)【默认值是 inline,但是浏览器样式表设置了其他默认值。例如,“div”元素的 display 属性默认值是 block。】
定位方案
- 普通:根据对象在文档中的位置进行定位,也就是说对象在呈现树中的位置和它在 DOM 树中的位置相似,并根据其框类型和尺寸进行布局。
- 浮动:对象先按照普通流进行布局,然后尽可能地向左或向右移动。
- 绝对:对象在呈现树中的位置和它在 DOM 树中的位置不同。
定位方案是由“position”属性和“float”属性设置的。
-
如果值是 static 和 relative,就是普通流
-
如果值是 absolute 和 fixed,就是绝对定位
static 定位无需定义位置,而是使用默认定位。对于其他方案,网页作者需要指定位置:top、bottom、left、right。
框类型
block 框:形成一个 block,在浏览器窗口中拥有其自己的矩形区域。
inline 框:没有自己的 block,但是位于容器 block 内。
block 采用的是一个接一个的垂直格式,而 inline 采用的是水平格式。nline 框放置在行中或“行框”中。这些行至少和最高的框一样高,还可以更高,当框根据“底线”对齐时,这意味着元素的底部需要根据其他框中非底部的位置对齐。如果容器的宽度不够,inline 元素就会分为多行放置。在段落中经常发生这种情况
定位
相对定位:先按照普通方式定位,然后根据所需偏移量进行移动。
浮动:浮动框会移动到行的左边或右边。有趣的特征在于,其他框会浮动在它的周围。
绝对定位和固定定位的布局是准确定义的,与普通流无关。元素不参与普通流。尺寸是相对于容器而言的。在固定定位中,容器就是可视区域。
分层展示
这是由 z-index CSS 属性指定的。它代表了框的第三个维度,也就是沿“z 轴”方向的位置。一般来说,后面的节点会前面的节点后绘制,也就是说后面的节点会覆盖前面的节点,但是如果设置z-index,就可能改变覆盖顺序。