在上一篇文章中对一些CSS问题进行回答,发现不少都是知其然不知其因此然,而写文章的目的就是改变知其然不知其因此然的状态(由于这样的简历,人家不要我),而阅读了tali garsiel的一篇关于浏览器工做原理的中文版,以为文章不是很适合XX阅读,读起来比较生涩,因此花了差很少XX小时整理了这篇文章。css
参考文章: 浏览器的工做原理:新式网络浏览器幕后揭秘html
做者注:
英文原文:howbrowserswork前端
经过网络引擎获取到文档;html5
开始解析文档,在遇到脚本时当即执行脚本,文档中止解析,若是是外部脚本则等待资源加载并执行完成,外部脚本可经过加defer或者async告诉浏览器异步处理,这样不会打断解析;面试
布局,为每一个节点分配一个应出如今屏幕上的确切坐标;算法
绘制,呈现引擎会遍历呈现树,由用户界面后端层将每一个节点绘制出来;chrome
显示,值得注意的是这一步并不会等到文档解析完成,会将部分已解析的文档尽快显示。后端
做者注:不少现代浏览器都有进行预解析优化,即主解析器解析DOM树,使用其余线程解析外部资源的引用。浏览器
做者注:为何会中止解析?由于脚本在文档解析阶段会请求样式信息。若是当时尚未加载和解析样式,脚本就会得到错误的回复,这样显然会产生不少问题。微信
呈现引擎从网络层获取文档数据,拥有数据后的呈现引擎开始解析HTML文档,并将各标记逐个转化为内容树上的DOM节点。同时也会解析外部CSS 文件以及样式元素中的样式数据,HTML 中这些带有视觉指令的样式信息将用于建立另外一个树结构将建立另外一个树结构:呈现树。
呈现树构建完毕以后,进入“布局”处理阶段,也就是为每一个节点分配一个应出如今屏幕上的确切坐标。下一个阶段是绘制- 呈现引擎会遍历呈现树,由用户界面后端层将每一个节点绘制出来。
在呈现引擎中解析是很是重要的环节。解析文档便是将文档转化为有意义的结构,解析后获得的结果一般表明了文档结构的节点树,被称做解析树或语法树。
而解析的过程通常为词法分析和语法分析,词法分析便是大量的标记过程,词法分析器根据特定的字典(语言的词汇)对输入内容进行标记;语法分析便是应用语言语法的过程。不一样语言拥有不一样的解析器,在这里不作多的赘述,若是想了解更多,那就去了解吧。
在浏览器中,有HTML解析器,CSS解析器,JavaScript解析器等。
HTML解析器输出的解析树是由DOM元素和属性节点构成的树结构(内容树或DOM树)。
具体解析算法:标记化和树构建,分别有标记生成器和树构建器完成
做者注:在这里提到的”标记”即为咱们常说的标签
在查看具体算法以前,咱们须要先了解一些基础的东西,便于对算法的理解。一个HTML元素包括其起始标记,结束标记,属性名称和属性值,以及内容。标记化经过状态机来表示,每个状态接收输入的一个或多个字符,并根据这些字符更新状态。
具体算法以下:
初始状态:数据状态
遇到” < ”:标记打开状态
遇到字符:标记名称状态(上一状态为标记打开状态) // 这里原文说的是a-z
遇到“/ “:建立end tag token(上一状态为标记打开状态)
遇到字符:发送字符标记(上一状态为数据状态)
遇到” > ”:发送当前标记,修改状态为数据状态(上一状态为标记打开状态)
在解析器标记化时,也会进行树构建。DOM树以Document为根节点,经过树构建器接收标记生成器发送的标记,并根据规范匹配并建立对应的DOM元素,建立的元素将被添加到DOM树以及开放的堆栈中。
原文注:此堆栈用于纠正嵌套错误和处理未关闭的标记
树构建的流程好像搞得半懂不懂的,有机会研究下源码,再记录吧。这里先大概记录下如今的理解吧。首先树构建器接收到标记,会根据状态判断以及容错规则判断处理方式,重复该操做。
状态处理规则:InitialMode,BeforeHTMLMode, BeforeHeadMode, InHeadMode, AfterHeadMode, InBodyMode,InTableMode, InCapationMode, InColumnGroupMode等,在这些状态时会根据接收到的标记进行判断,不符合规范则系统隐式建立符合规范的元素,附加到DOM树,并从新处理接收到的标记。
容错处理规则:
如下片断原文使用的引用,引用自W3C规范
1.明显不能在某些外部标记中添加的元素。在此状况下,咱们应该关闭全部标记,直到出现禁止添加的元素,而后再加入该元素。
2.咱们不能直接添加的元素。这极可能是网页做者忘记添加了其中的一些标记(或者其中的标记是可选的)。这些标签可能包括:HTML HEAD BODY TBODY TR TD LI(还有遗漏的吗?)。
3.向inline 元素内添加 block 元素。关闭全部inline 元素,直到出现下一个较高级的 block 元素。若是这样仍然无效,可关闭全部元素,直到能够添加元素为止,或者忽略该标记。
CSS解析和HTML不一样,HTML因为要求处理较为“宽容“,容许省略某些隐式添加的标记,有时还能省略一些起始或者结束标记等等,他的语法不是与上下文无关的语法,以致于HTML 并不能很容易地用解析器所需的与上下文无关的语法来定义。而CSSCSS 是上下文无关的语法,可使用常规解析器进行解析,浏览器的CSS解析器根据规范生成,如WebKit使用Flex和Bison解析器生成器,经过
CSS 语法文件自动建立解析器。向解析器输入css样式,解析器则输出相应的解析树。
呈现树构建时间在文章中呈现引擎工做流程已介绍,它的做用就是按照正确的顺序绘制内容。Firefox 将呈现树中的元素称为“框架”。WebKit 使用的术语是呈现器或呈现对象。在原文中做者使用“呈现器”进行接下来的介绍,为了方便理解咱们强制记住如下使用的“呈现器”即表示呈现树种的元素。
呈现树的构建基于DOM元素和CSS规则,而呈现树则是布局和绘制的基础,呈现树经过构建与DOM节点的呈现器以及计算相关节点的CSS规则构建。
呈现构造器根据css中定义的“display”属性以及元素类型建立不一样的呈现器类型,每一个呈现器都表明一个矩形框,可一般对应于相关节点的css框,包含了宽度,高度,位置等几何信息。这里根据不一样元素主要是一些特殊元素,如表单,表格等,这些呈现器包含一些除了几何信息外的信息。
做者注:原本还觉得呈现器的矩形框便是咱们常说的盒子呢,后来才知道CSS框模型是布局后的矩形框。因此我好像猜对了。
在构建呈现树时,须要经过计算每一个元素的样式属性来计算每一个呈现器的可视化属性。其中样式来源包括浏览器的默认样式表、由网页提供的样式表以及由用户提供给浏览器的用户样式表。
固然计算样式有不少难点,好比数据复杂的结构,这里就不列出来了,有需求的能够阅读原文,这里只简单介绍浏览器如何计算。
样式的计算会用CSS解析器的解析结果,系统根据以样式规则最右边的选择器为键(这也解释了为何CSS是从右向左匹配)将CSS规则(总体)添加到不一样的选择器哈希表,如ID表,类表等。计算是只要从哈希表中提取元素相关的规则便可(这能够不考虑与元素无关的规则,从而减小计算)。剩下的工做就是从提取的规则中判断真正匹配元素的规则。
做者注:这里所说的CSS规则就是咱们开发者写的CSS规则,以下便是一条规则
#my-container { color: #fff; background: #fff; }
样式层叠顺序规则:浏览器申明<用户普通声明<做者普通声明<做者重要声明<用户重要声明。做者即开发者,用户即浏览器使用者。
特异性规则:按a-b-c-d顺序。a表示是否为style属性,是则为1,不然为0;b表示选择器ID的个数;c表示选择器中类,伪类,以及其余元素的个数;d表示元素名称和伪元素的个数。如form#form1 input[type=number] + label.input-label:before {…} 可表示为a=0 b=1 c=2 d=4
这也就导出了为何属性可经过标记1000这样的权重计算规则。固然浏览器内部具体如何实现计算,仍是不知道,对于普通开发者也不必知道,这也足够解决开发问题了。
做者注: 能够看关于别人前端面试的问题的回答(CSS篇)中的关于CSS权重计算的问题
呈现器与DOM元素是相对应,但并不是一一对应的。非可视化的元素不会插入呈现树,如HRAD,display属性为“none”的元素。而像“select”这样的复杂结构元素,则须要多个呈现器。
在Firefox中,呈现引擎(原文用的”系统”)经过注册展现层做为侦听器。经过展现层委托给FrameConstructor来解析样式并建立呈现器。在WebKit中,把解析样式和建立呈现器的过程称为”附加”(原文使用了“附加(Attachment)”,我的以为该词不翻译更好)。附加是同步进行的,经过调用新节点的“attach”方法把节点插入DOM树,而调用节点的”attach”方法会根据建立呈现器规则判断是否须要建立呈现器,须要则建立呈现器。
做者注: 图片原文地址
呈现器在建立完成并添加都呈现树时并不包含位置和大小信息,把计算这些值得过程成为布局或重排。HTML采用基于流的布局模型,意味着大多数状况下只要一次遍历就能计算出几何信息(位置,大小),处于流中靠后位置的元素一般不会影响靠前位置元素的几何特征。
布局是一个递归的过程,从根呈现器(HTML元素)开始(0,0位置,浏览器左边),递归遍历部分或所有呈现器,并经过调用须要布局的子呈现器的”layout”或“reflow”方法计算子呈现器的几何信息(父元素被子元素撑开)。
而为了不对全部细微的改动都进行总体布局(重绘),浏览器采用了一种”dirty”系统,对发生改变的呈现器,浏览器将其标记为“dirty”,表示须要从新布局。有两种标记“dirty”和“children are dirty”。“children are dirty”表示尽管呈现器自身没有改变,可是它至少一个子代须要从新布局(那么这种状况究竟是只布局子代仍是会布局这个呈现器自己和其子代呢?)。
当全部呈现器的全局样式(如字体大小更改)或者屏幕大小发生改变时会触发全局布局,当部分呈现器发送改变(标记为“dirty”)时会触发增量布局,如脚本操做DOM。
增量布局是异步执行的,Firefox使用队列,由调度程序触发布局,而WebKit使用定时器对呈现树进行遍历,对标记为“dirty”的呈现器进行布局。全局布局每每是同步的。
父呈现器肯定本身高度
父呈现器处理子呈现器,为其设置坐标,在子呈现器为“dirty”或全局布局时,为子呈现器计算高度
父呈现器根据子呈现器高度以及边框,边距,补白等信息累加设置自身高度(递归的过程)。
将“dirty”标记改成false
若是在布局过程当中,呈现器须要换行,则会中止布局,并告知父呈现器须要换行,父呈现器则会建立额外的呈现器,并进行布局。
系统遍历呈现树,并调用呈现器的“paint”方法,将呈现器的内容显示在屏幕上的过程称为绘制。
跟布局同样,绘制也分为全局绘制和增量绘制。在增量绘制中,部分呈现器发生了更改,可是不会影响整个树。更改后的呈现器将其在屏幕上对应的矩形区域设为无效,这致使OS 将其视为一块“dirty 区域”,并生成“paint”事件。
原文在这里对Firefox和chrome进行了介绍
在介绍绘制顺序前,先介绍下CSS的分层展现。分层有CSS的z-index属性指定,它表明了框的第三维度,也就是沿“z轴”方向的位置。这些框分散在多个堆栈(称为堆栈上下文),在每一个堆栈中,会首先绘制后面的元素,而后再顶部绘制前面的元素,以便更靠近用户,对于重叠的元素,则最后绘制的在最前面。具备z-index属性的框造成了本地堆栈,视口具备外部堆栈(因此若是“dirty”会触发增量绘制?)。
接着介绍绘制顺序,绘制的顺序就是元素进入堆栈的顺序,这些堆栈会从后向前绘制,所以这样的顺序会影响绘制。而块呈现器的堆栈顺序为:背景色,背景图片,边框,子代,轮廓。
呈现引擎采用单线程,除网络操做外的全部操做都在单线程中进行。除chrome浏览器该线程为标签页线程,其余浏览器都使用浏览器主线程。网络操做可由多个并行线程执行。
浏览器的主线程是事件循环,是一个无限循环,永远等待时间发生,并进行处理。
虽然原文总的来讲看了两遍,可是以为仍是不少地方似懂非懂,但愿有机会查看一次他的源代码吧,不只仅是弄清楚浏览器如何运行,更是但愿可以重中学到其余工程师如何处理结构如此复杂的数据。
最后附上原文的两张主流程图
成本价:约¥500
价值几何:看官来讲
欢迎关注微信号: 成长之路。提出你的问题,交给我来回答。一块儿踏上成长之路,在文章评论中留言你的问题也有机会被选中哦。
若是你发现文中有错误的地方,请指出,我将及时修改。
整理的很差,很是有幸被你看到,请指点。