浏览器的工作原理是非常重要的基础知识,而且内容非常多,有一篇文章How browsers work非常著名,其内容也十分详实,有中文版本前端必读:浏览器内部工作原理。在此基础上,我将一些要点用我自己能理解的话总结一遍,一是梳理一遍整个的过程,另外是方便今后的复习。
总论
浏览器的主要构成
浏览器的主要构成是用户界面、浏览器引擎、渲染引擎、网络、UI后端、JS解释器、数据存储。在chorme中,每个标签页都是独立的进程(意味着单个标签页的崩溃不会影响到其他标签页)
渲染引擎
渲染引擎
面试常见的一个问题是浏览器的内核有哪些,浏览器的内核包括渲染引擎和JS引擎,但随着JS引擎越来越独立,内核就倾向只指代渲染引擎了。
浏览器内核 | 常见浏览器 |
---|---|
Trident内核 | IE、傲游、世界之窗浏览器、Avant、腾讯TT、360、Netscape 8、NetCaptor、Sleipnir、GOSURF、GreenBrowser和KKman等 |
Gecko内核 | Firefox、Netscape6至9 |
WebKit内核 | Safari、chrome、Opera |
EdgeHTML内核 | Edge |
渲染的主流程
解析html以构建dom树 -> 构建render树 -> 布局render树 -> 绘制render树
- 渲染引擎解析html,构建“DOM tree”
- CSS文件及style标签中的样式信息以及html中的可见性指令构建CSSOM
- DOM tree和CSSOM一起构建Render树
- Render树是一些含有各种属性的矩形,根据某种规则显示在屏幕上。
- 构建了Render树后,会布局Render树,这一过程确定每个节点的坐标
- 布局之后是绘制,遍历Render树,并使用UI后端层绘制
-
webkit主流程
-
Geoko主流程
- Geoko中的Frame树就是Render树,Reflow就是Layout,仅是名字不同
- Geoko在解析HTMl和构建DOM树之间有一层Content Sink(内容接收器),用于生成DOM元素,这是webkit所没有的、
解析与DOM树构建
几个概念:
- 解析:就是将一个文档的结构转换为代码可以理解和使用的结构(通常是文档结构的节点树),称为解析树或语法树。
- 文法:解析要基于文法,文法由词汇和语法规则组成,此处的文法是上下文无关文法(“DTD”不是上下文无关,因此后面介绍的解析方法不适合HTML,但可用于CSS和JS)。
解析的过程(由浅入深)
- 解析的两个子过程——语法分析及词法分析
-
词法分析:将输入分解为符号。
- 符号:是语言的词汇表——基本有效单元的集合。
- 词法分析器将输入分解为合法的符号
-
语法分析:指对语言应用语法规则。
- 解析器:根据语言的语法规则分析文档结构,从而构建解析树
-
词法分析:将输入分解为符号。
- 解析的过程进一步可视为四步:源文档 -> 词法分析 -> 语法分析 -> 解析树
- 解析的更细致的迭代过程是:
- 词法分析器得到符号,传给解析器
- 解析器用符号匹配语法规则
* 若匹配上规则,则符号对应的节点将被添加到解析树上 * 若没有匹配上规则,解析器将在内部保存该符号,然后从词法分析器取下一个符号,再次匹配规则 * 若在之后能使内部符号匹配上规则,则符号对应的节点将被添加到解析树上 * 若到最后都没有匹配上规则,解析器将抛出一个异常,这意味着文档无效或是包含语法错误。
- 最终得到解析树或异常
- 解析一般在转换(将输入文档转换为另一种格式)中使用,因此,解析树可能不是最终结果,比如编译。其过程为:源码 -> 解析 -> 解析树 -> 转换 -> 机器码
解析器类型
- 自顶向下解析,查看语法的最高层结构并试着匹配其中一个
- 自底向上解析,从输入开始,逐步将其转换为语法规则,从底层规则开始直到匹配高层规则
- 前文有说过,之前提到的都是上下文无关文法,但HTML的格式定义--“DTD”不是上下文无关,所以传统解析方式(自顶向下或自底向上)都不适用于HTML(可用于CSS和JS)
DOM
- 输出的树,也就是解析树,是由DOM元素节点(包括文本节点和属性节点)组成的。
- 树的根是“document”对象
- DOM和标签基本是一一对应的关系
HTML解析
- HTML解析包括两个阶段——符号化(tokeniser)及构建树(tree construction)(和之前的具体算法不一样,但几个过程是类似的)
- 符号化(可和传统的词法分析类比):是词法分析的过程,符号识别器将输入解析为符号(html的符号包括开始标签、结束标签、属性名及属性值)。将其传递给树构建器。
- 构建树(可和传统的语法分析类比):树构造器处理传来的符号,根据规范每个符号会创建对应的Dom元素,这些元素除了会被添加到Dom树上,还将被添加到开放元素堆栈中。这个堆栈用来纠正嵌套的未匹配和未闭合标签(HTML“宽容”的原因)。
-
HTML解析流程
解析结束时的处理
- 在此阶段,浏览器会将文档标注为交互状态(interactive)
- 开始解析那些处于“deferred”模式的脚本,也就是那些应在文档解析完成后才执行的脚本
- 然后,文档状态将设置为“完成”(complete),一个“加载”(load)事件将随之触发
CSS解析
- css属于上下文无关文法,可以用前面所描述的解析器(自顶向下或自底向上)来解析
- Webkit的CSS解析器将每个css文件解析为样式表对象,每个对象包含css规则,css规则对象包含选择器和声明对象,以及其他一些符合css语法的对象
处理脚本及样式表的顺序
脚本
- 解析到一个script标签时立即解析执行脚本,并阻塞文档的解析直到脚本执行完。
- 如果脚本是外引的,则网络必须先请求到这个资源——这个过程也是同步的,会阻塞文档的解析直到资源被请求到。
- 开发者可以将脚本标识为defer,以使其不阻塞文档解析,并在文档解析结束后执行(
<script defer src="script.js"></script>
)。Html5增加了标记脚本为异步的选项,以使脚本的解析执行使用另一个线程。(<script async src="script.js"></script>
)
预解析
- 当执行脚本时,另一个线程解析剩下的文档,并加载后面需要通过网络加载的资源。这种方式可以使资源并行加载从而使整体速度更快
- 需要注意的是,预解析并不改变Dom树,它将这个工作留给主解析过程,自己只解析外部资源的引用,比如外部脚本、样式表及图片
样式表
- 因为执行脚本可能会请求样式信息,如果此时样式还没有被加载和解析完成,那么脚本可能出错。所以样式表会阻塞脚本执行(不会阻塞外部脚本的加载)
- Firefox在存在样式表还在加载和解析时阻塞所有的脚本
- Chrome只在当脚本试图访问某些可能被未加载的样式表所影响的特定的样式属性时才阻塞这些脚本
渲染树的构建
- DOM树构建完后,开始构建渲染树。Firefox将渲染树中的元素称为frames,WebKit则用renderer或渲染对象来描述这些元素。
- 每个渲染对象用一个和该节点的css盒模型相对应的矩形区域来表示,包含诸如宽、高和位置之类的几何信息
渲染树和Dom树的关系
- 不可见的Dom元素(比如
<head>
)不会被插入渲染树,display属性为none的元素也不会在渲染树中出现 - 当文本因为宽度不够而折行时,新行将作为额外的渲染元素被添加
- 一个行内元素只能仅包含行内元素或仅包含块状元素,在存在混合内容时,将会创建匿名的块状渲染对象包裹住行内元素。
创建树的流程
- Firefox用一个监听器监听DOM,Frame Constructor计算样式并创建Frame
- Webkit中每个Dom节点有一个attach方法,调用新节点的attach方法将节点插入到Dom树中
样式计算
- 创建渲染树需要计算出每个渲染对象的可视属性,这可以通过计算每个元素的样式属性得到
- 样式包括各种来源的样式表,行内样式元素及html中的可视化属性(例如bgcolor),可视化属性转化为css样式属性。
样式表的级联顺序
具有同等级别的声明将根据specifity以及它们被定义时的顺序进行排序。
- 浏览器声明
- 用户声明
- 作者的一般声明
- 作者的important声明
- 用户important声明
Specifity
- 如果声明来自style属性,而不是一个选择器的规则,则计1,否则计0(=a)
- 计算选择器中id属性的数量(=b)
- 计算选择器中class及伪类的数量(=c)
- 计算选择器中元素名及伪元素的数量(=d)
连接a-b-c-d四个数量将得到specifity。四级(a、b、c、d)之间并不是简单的相加关系。同一级(例如:a对a)的才具有可比关系
布局
当渲染对象被创建并添加到树中,它们并没有位置和大小,计算这些值的过程称为layout或reflow。
绘制
- 遍历渲染树并调用渲染对象的paint方法将它们的内容显示在屏幕上,绘制使用UI基础组件
- 一个块渲染对象的堆栈顺序是:
1. 背景色
2. 背景图
3. border
4. children
5. outline
渲染引擎的线程
- 渲染引擎是单线程的,除了网络操作以外,几乎所有的事情都在单一的线程中处理,在Firefox和Safari中,这是浏览器的主线程,Chrome中这是tab的主线程。
- 网络操作由几个并行线程执行,并行连接的个数是受限的(通常是2-6个)。
事件循环
浏览器主线程是一个事件循环,它被设计为无限循环以保持执行过程的可用,等待事件(例如layout和paint事件)并执行它们