浏览器工作原理

HTML页面加载和解析流程

  1. 用户输入网址(假设是个html页面,并且是第一次访问),浏览器向服务器发出请求,服务器返回html文件;
  2. 浏览器开始载入html代码,发现<head>标签内有一个<link>标签引用外部CSS文件;
  3. 浏览器又发出CSS文件的请求,服务器返回这个CSS文件;
  4. 浏览器继续载入html中<body>部分的代码,并且CSS文件已经拿到手了,可以开始渲染页面了;
  5. 浏览器在代码中发现一个<img>标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续渲染后面的代码;
  6. 服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码;
  7. 浏览器发现了一个包含一行Javascript代码的<script>标签,赶快运行它;
  8. Javascript脚本执行了这条语句,它命令浏览器隐藏掉代码中的某个<div> (style.display=”none”)。突然少了这么一个元素,浏览器不得不重新渲染这部分代码;
  9. 终于等到了</html>的到来,浏览器泪流满面……
  10. 等等,还没完,用户点了一下界面中的“换肤”按钮,Javascript让浏览器换了一下<link>标签的CSS路径;
  11. 浏览器召集了在座的各位<div><span><ul><li>们,“大伙儿收拾收拾行李,咱得重新来过……”,浏览器向服务器请求了新的CSS文件,重新渲染页面。

一、浏览器介绍

浏览器的主要构成(High Level Structure)

  1. 用户界面 - 包括地址栏、后退/前进按钮、书签目录等,也就是你所看到的除了用来显示你所请求页面的主窗口之外的其他部分。
  2. 浏览器引擎 - 用来查询及操作渲染引擎的接口。
  3. 渲染引擎 - 用来显示请求的内容,例如,如果请求内容为html,它负责解析html及css,并将解析后的结果显示出来。
  4. 网络 - 用来完成网络调用,例如http请求,它具有平台无关的接口,可以在不同平台上工作。
  5. UI后端 - 用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口。
  6. JS解释器 - 用来解释执行JS代码。
  7. 数据存储 - 属于持久层,浏览器需要在硬盘中保存类似cookie的各种数据,HTML5定义了web database技术,这是一种轻量级完整的客户端存储技术

不同于大部分浏览器,Chrome为每个Tab分配了各自的渲染引擎实例,每个Tab就是一个独立的进程。
浏览器主要组件

二、渲染引擎(The rendering engine)

  • Firefox:Geoko——Mozilla自主研发的渲染引擎
  • Safari和Chrome:webkit

渲染主流程(The main flow)

显示应用CSS之后的html及图片

解析完一部分内容就显示一部分内容,并不会等到所有的html都解析完成之后再去构建和布局render树。同时,可能还在通过网络下载其余内容。

  1. 获得所请求文档的内容(通过网络,通常以8K分块的方式完成)
  2. 构建dom树 –> 构建render树 –> 布局 –> 绘制
  • 构建dom树(解析html,将 标签 转化为内容树中的DOM节点)
  • 构建render树(解析外部CSS文件、style标签中的样式 + 元素可见性):将包含有颜色和大小等属性的 矩形,按照正确的顺序显示到屏幕上
  • 布局:确定每个节点在屏幕上的确切 坐标
  • 绘制:遍历render树,使用UI后端层绘制每个节点

webkit主流程
Mozilla的Geoko渲染引擎主流程

三、具体流程

1. 构建DOM树

浏览器一般解析方式

源码 –(解析)–> 解析树 –(转换)–> 机器码

a. 解析:将文档转化为编码可以理解的结果

词汇表:正则表达式定义
语法规则(上下文无关文法):BNF格式定义

  • 词法分析器:输入 –> 词汇表(跳过无关字符,空白和换行等)
  • 解析器:词汇表 + 语法规则(匹配) –> 解析树

    匹配成功:词汇表对应的节点添加到解析树上,请求下一个词汇表

    匹配失败:保存词汇表,请求下一个词汇表(直到所有保存的词汇表都匹配到语法规则)

    最终匹配失败:抛出异常(文档无效/语法错误)
解析器类型
  • 自顶向下:从最高层结构开始
  • 自底向上:从输入开始(想象一个指针首先指向输入开始处,向右移动,逐渐简化为语法规则)
解析生成器

解析生成器:指定词汇表和语法规则,自动生成解析器
(手动创建性能好的解析器并不容易)

webkit使用:

  • Flex(创建语法分析器)—— 自顶向下
  • Bison(创建解析器)—— 自底向上

    b. 转换:转换为机器码

    c. 举例:解析数学表达式

例如:解析“2+3-1”

词汇表和语法规则

词汇表(正则表达式定义):整数、加号、减号
语法规则(BNF格式定义):

  • 语法基本单元:表达式、term、操作符
  • 表达式:操作符连接的2个term
  • 操作符:加号、减号
  • term:整数、表达式
词汇表 语法规则
2 term(整数)
+ 操作符
2+3 term(表达式)
2+3-1 term(表达式)
2++ 文档无效
解析过程
  • 自顶向下:识别表达式的过程中匹配了其他规则,但出发点是最高层规则
    | 词汇表 | 语法规则 |
    | :—– | :———- |
    | 2+3 | term(表达式)|
    | 2+3-1 | term(表达式)|

  • 自底向下:
    | 输入 | 栈 |
    | :—– | :—————– |
    | 2+3-1 | term(整数) |
    | +3-1 | term(整数) 操作符 |
    | 3-1 | 表达式 |
    | -1 | 表达式 操作符 |
    | 1 | 表达式 |

解析HTML

HTML不是上下文无关文法,不可以用一般解析方式解析

  • HTML的词汇表和语法:由W3C组织制定,由DTD(Document Type Definition文档类型定义)定义
    • HTML DTD:标准模式遵守规范,混杂模式兼容以前内容
  • HTML解析器:将html标识解析为解析树,不能使用正则解析

DOM:DOM元素 + 属性节点

DOM(文档对象模型):html文档的对象表示,作为html元素的外部接口供js等调用

  • 树的根是“document”对象
  • DOM和标签基本是一一对应的关系

举例:

1
2
3
4
5
6
7
8
<html>
<body>
<p>
Hello DOM
</p>
<div><img src=”example.png” /></div>
</body>
</html>

HTML解析算法

a. 符号化【符号识别器】:词法分析,输入 –> 符号(开始标签、结束标签、属性名、属性值)

符号识别算法:状态机表示(当前符号 + 构建树状态 –> 下一个状态)

b. 构建树【树构建器】:符号 –> DOM元素,添加到DOM树和开放元素堆栈(纠正嵌套的未匹配、未闭合标签)

树的构建算法:状态机表示,插入模式

c. 解析结束的处理
  • 标记文档:可交互
  • 解析脚本
  • 设置文档状态:完成;触发load事件
    浏览器容错(略)
    浏览器都具有错误处理的能力,但还是写格式良好的html吧

解析CSS

css属于上下文无关文法,可以用一般解析方式解析

  • CSS的词汇表和语法:CSS规范定义
    • 词汇表:正则表达式定义
    • 语法:BNF定义

一个规则集合具有一个或是可选个数的多个选择器,这些选择器以逗号和空格(S表示空格)进行分隔。每个规则集合包含大括号及大括号中的一条或多条以分号隔开的声明。声明和选择器在后面进行定义。

Webkit CSS解析器

  • Flex(创建语法分析器)—— 自顶向下
  • Bison(创建解析器)—— 自底向上

每个css文件 –> 样式表对象 –> css规则 –> 选择器 + 声明对象 + 其他对象

解析脚本

同步:阻塞文档解析

  • 遇到script标签,立即解析执行脚本
  • 通过网络请求外部引入脚本

  • 设置脚本为defer:不阻塞文档解析,脚本在文档解析后执行

  • HTML5可以标记脚本为异步,是脚本的解析执行使用另一线程

问题:

脚本可能在文档的解析过程中请求样式信息,如果样式还没有加载和解析,脚本将得到错误的值
解决:

  • Firefox:在样式表还在加载和解析时阻塞所有的脚本
  • Chrome:只在脚本访问未加载的样式时才阻塞这些脚本

    预解析

    预解析:执行脚本时,另一线程解析剩下的文档,加载需要通过网络加载的资源 —— 使资源并行加载,加快整体速度

预解析并不改变DOM树,预解析只解析外部资源(外部脚本、样式表、图片)

2. 构建render树

render树:文档的可视化表示,由可见元素组成,以正确的顺序绘制文档内容

render树中的元素:

  • Firefox:frames
  • Webkit:renderer、渲染对象

渲染对象:用与该节点的css盒模型相对应的矩形区域来表示,包含诸如宽、高和位置之类的几何信息。盒模型的类型受该节点相关的display样式属性的影响。

渲染对象知道怎么布局及绘制自己及它的children。

渲染对象与DOM元素不是一对一的:

  • 不会被插入渲染树
    • 不可见的Dom元素,例如head元素
    • display属性为none的元素(visibility属性为hidden的元素将出现在渲染树中)
  • 1个DOM元素 - 多个渲染对象
    • select元素有3个渲染对象:显示区域、下拉列表、按钮
    • 文本因为宽度不够而折行:新行将作为额外的渲染元素被添加
    • 不规范的html:一个行内元素只能仅包含行内元素或仅包含块状元素,存在混合内容时,将会创建匿名的块状渲染对象包裹住行内元素
  • 渲染对象和所对应的DOM元素不在树上相同的位置
    • 浮动和绝对定位:渲染树上标识出真实的结构,并用一个占位结构标识出它们原来的位置

3. 布局

4. 绘制

您的支持将鼓励我继续创作!
------本文结束感谢阅读------