作为一个小白很早就关注前端的相关知识,虽然在各类博客汲取不少知识,但感觉还是需要亲自整理一些知识点,才能更加熟练
运用学到手的东西。在去年校招期间有招聘方涉及到浏览器的渲染,当时只懂一点js+css,了解的不够深入,尽管在有大神整理出
了清晰的文章,在下还是决定亲自读一下《浏览器工作原理》,话不多说,先从浏览器构成开始。
浏览器的主要构成(High Level Structure)
浏览器的主要组件包括:
1. 用户界面 - 包括地址栏、后退/前进按钮、书签目录等,也就是你所看到的除了用
来显示你所请求页面的主窗口之外的其他部分。
2. 浏览器引擎 - 用来查询及操作渲染引擎的接口。
3. 渲染引擎 - 用来显示请求的内容,例如,如果请求内容为html,它负责解析html
及css,并将解析后的结果显示出来。
4. 网络 - 用来完成网络调用,例如http请求,它具有平台无关的接口,可以在不同
平台上工作。
5. UI后端 - 用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台
的通用接口,底层使用操作系统的用户接口。
6. JS解释器 - 用来解释执行JS代码。
7. 数据存储 - 属于持久层,浏览器需要在硬盘中保存类似cookie的各种数据,
HTML5定义了web database技术,这是一种轻量级完整的客户端存储技术。
如图 1.1 所示
图 1.1
在众多浏览器中,Chrome 浏览器为每个Tab分配了各自的渲染引擎实例,每个Tab就是一个独立的进程。什么是渲染引擎?
渲染引擎(The rendering engine)
主流程(The main flow)
在取得通过网络请求的文档以后,渲染引擎的基本流程:
解析html构建dom树
↓
构建render树
↓
布局render树
↓
绘制render 树
如图 1.2 所示
图1.2
渲染伊始,解析html标签,生成DOM结构,然后解析CSS层叠样式表,根据结果构建Render树。Render树由一些包含有颜色和大小等属性的矩
形组成,按顺序排列,然后开始布局,确定结点的坐标,最后绘制,遍历Render树,使用UI后端绘制。
注意这个过程是边解析边显示的,并非解析完所有结构才显示网页内容。
渲染树和Dom树的关系(The render tree relation to the DOM tree)
渲染对象和元素相对应,但这种对应关系不是一对一的,不可见的DOM元素不会被插入渲染树,例如head元素。另
外,display属性为none的元素也不会在渲染树中出现(visibility属性为hidden的元素将出现在渲染树中)。
还有一些DOM元素对应几个可见对象,它们一般是一些具有复杂结构的元素,无法用一个矩形来描述。例如,select元素有三
个渲染对象——一个显示区域、一个下拉列表及一个按钮。同样,当文本因为宽度不够而折行时,新行将作为额外的渲染元素被添
加。另一个多个渲染对象的例子是不规范的html,根据CSS规范,一个行内元素只能仅包含行内元素或仅包含块状元素,在存在混
合内容时,将会创建匿名的块状渲染对象包裹住行内元素。一些渲染对象和所对应的DOM节点不在树上相同的位置,例如,浮动和
绝对定位的元素在文本流之外,在两棵树上的位置不同,渲染树上标识出真实的结构,并用一个占位结构标识出它们原来的位置。
如图 1.3 所示
图 1.3
创建树的流程(The flow of constructing the tree)
处理html和body标签将构建渲染树的根,这个根渲染对象对应被 CSS规范称为containing block的元素——包含了其他所有
块元素的顶级块元素。它的大小就是viewport——浏览器窗口的显示区域,文档指向的渲染对象。
样式计算(Style Computation)
样式数据是非常大的结构,保存大量的样式属性会带来内存问题。不进行优化,找到每个元素匹配的规则会导致性能问题。
Specifity
CSS2规范中定义的选择符specifity如下:
如果声明来自style属性,而不是一个选择器的规则,则计1,否则计0(=a)
计算选择器中id属性的数量(=b)
计算选择器中class及伪类的数量(=c)
计算选择器中元素名及伪元素的数量(=d)
连接a-b-c-d四个数量(用一个大基数的计算系统)将得到specifity,根据改值确定优先级。
布局
当渲染对象被创建并添加到树中,它们并没有位置和大小,计算这些值的过程称为layout(webkit) 或reflow(Gecko)。
Html使用基于流的布局模型,意味着大部分时间,可以以单一的途径进行几何计算。流中靠后的元素并不会影响前面元素的几何特
性,所以布局可以在文档中从右向左、自上而下的进行。也存在一些例外,比如html tables。
坐标系统相对于根frame,使用top和left坐标。布局是一个递归的过程,由根渲染对象开始,它对应html文档元素,布局
继续递归的通过一些或所有的通过一些或所有的frame层级,为每个需要几何信息的渲染对象进行计算。所有的渲染对象都有一
个layout或reflow方法,每个渲染对象调用需要布局的children的layout方法。
Dirty bit系统
为了不因为每个小变化都全部重新布局,浏览器使用一个dirty bit系统,一个渲染对象发生了变化或是被添加了,就标记它及
它的children为dirty——需要layout。存在两个标识——个标识——dirty及children are dirty,children are dirty说明即使
这个渲染对象可能没问题,但它至少有一个没问题,但它至少有一个child需要layout。
全局和增量layout
当layout在整棵渲染树触发时,称为全局layout,这可能在下面这些情况下发生:
1.一个全局的样式改变影响所有的渲染对象,比如字号的改变。
2.窗口resize。
layout也可以是增量的,这样只有标志为dirty的渲染对象会重新布局(也将导致一些额外的布局)。增量layout会在渲染
对象dirty时异步触发,例如,当网络接收到新的内容并添加到容并添加到DOM树后,新的渲染对象会添加到渲染树中。
layout过程
layout一般有下面这几个部分:
1. parent渲染对象决定它的宽度
2. parent渲染对象读取chilidren,并:
a.放置child渲染对象(设置它的x和y)。
b.在需要时(它们当前为dirty或是处于全局layout或者其他原因)调用child渲染对象的layout,这将计算child的高度
c. parent 渲染对象使用 child 渲染对象的累积高度,以及margin和padding的高度来设置自己的高度-这将被parent渲染
对象的 parent 使用。
d.将dirty标识设置为false。
Line breaking
当一个渲染对象在布局过程中折行时,则暂停告诉它的 parent 需要折行,parent将创建额外的渲染对象并调用它的layout。
绘制(Painting)
绘制阶段,遍历渲染树并调用渲染对象的 paint 方法将它们的内容显示在屏幕上,绘制使用UI基础组件。
全局和增量
和布局一样,绘制也可以是全局的——绘制完整的树——或增量的。在增量的绘制过程中,一些渲染对象以不影响整棵树的方
式改变,改变的渲染对象使其在屏幕上的矩形区域失效,这将导致操作系统将其看作dirty区域,并产生一个paint事件,操作系
统很巧妙的处理这个过程,并将多个区域合并为一个。Chrome中,这个过程更复杂些,因为渲染对象在不同的进程中,而不是在
主进程中。Chrome在一定程度上模拟操作系统的行为,表现为监听事件并派发消息给渲染根,在树中查找到相关的渲染对象,重
绘这个对象(往往还包括它的 children)。
绘制顺序
这个是元素压入堆栈的顺序,这个顺序影响着绘制,堆栈从后向前进行绘制。
一个块渲染对象的堆栈顺序是:
1. 背景色
2. 背景图
3. border
4. children
5. outline
渲染引擎的线程
渲染引擎是单线程的,除了网络操作以外,几乎所有的事情都在单一的线程中处理,在Firefox和Safari中,这是浏览器的主
线程,Chrome中这是tab的主线程。网络操作由几个并行线程执行,并行连接的个数是受限的(通常是2-6个)。
定位策略Position scheme
1. normal-对象根据它在文档中位置定位,这意味它在渲染树和在DOM树中位置一致,根据它的盒模型和大小进行布局。
2. float-对象先像普通流一样布局,然后尽可能的向左或是向右移动。
3. absolute-对象在渲染树中的位置和DOM树中位置无关。
static和relative是normal,absolute和fixed属于absolute。在static定位中,不定义位置而使用默认的位置。其他策
略中,作者指定位置——top、bottom、left、right。
Layered representation
这个由CSS属性中的z-index指定,表示盒模型的第三个大小,即在z轴上的位置。Box分发到堆栈中(称为堆栈上文),
每个堆栈中靠后的元素将被较早绘制,栈顶靠前的元素离用户最近,当发生交叠时,将隐藏靠后的元素。堆栈根据z-index属性排
序,拥有z-index属性的box形成了一个局部堆栈,viewport有外部堆栈。