浏览器的主要组件为:css
渲染引擎负责渲染——即渲染HTML/XML文档或者图片(经过插件能够渲染PDF等等)。渲染引擎有html
浏览器从网络层获取请求的文档内容,而后开始渲染流程:前端
注意,渲染过程是渐进式的。浏览器会尽早展现文档内容,即不会在全部HTML文档解析完成后才会去构建render tree,而是部份内容被解析和展现,并继续解析和展现剩下的。html5
对chrome而言,渲染的具体流程是node
对firefox而言,git
script 是同步的github
web模型一直是同步的,即网页做者但愿引擎遇到<script>
标签时能够当即解析并执行——中止解析HTML,执行脚本(若是是外部脚本,先下载)。能够用defer
属性指定脚本是异步的——不会中止文档解析,在文档解析完成后执行。web
Speculative parsing(预解析)chrome
当执行脚本时,其它线程会解析剩下的文档,找出里面的外部资源(script/style/img)来提早加载(能够并行加载)。这种解析只是去查找须要加载的外部资源,不会修改content tree。数据库
因此咱们能够看到多个外部资源并行下载。
样式
样式表有不一样的模型。理论上,样式表不会更改 DOM tree,彷佛没有必要等待样式表并中止文档解析。但有个问题,若是在文档解析阶段,脚本访问样式信息怎么办?Firefox会在脚本加载和解析阶段禁止全部的脚本;对于 WebKit 而言,仅当脚本尝试访问的样式属性可能受还没有加载的样式表影响时,它才会禁止该脚本。
这就是为何推荐样式放在<head>
里而脚本放在<body>
底部。
构建 DOM tree的同时,浏览器还会构建另外一个树:渲染树(render tree)。这是由可视化元素按照其显示顺序而组成的树,也是文档的可视化表示。它的做用是保证按照正确的顺序来绘制内容。
渲染树的每一个节点(renderer)表明一个矩形区域——对应DOM元素的CSS Box。
renderer 和 DOM元素对应,但非一一对应。好比display:none
的元素没有对应的renderer;好比select
对应3个renderer(display area/drop down list box /button)。另外,根据css spec,一个inline元素只能包含一个block元素或者多个inline元素,若是不符规则,就会建立anonymous block renderer。
有些 renderers 与对应的 DOM 节点,在各自树中的位置不一样。好比浮动定位和绝对定位的元素,它们在normal flow以外,放置在树的其它地方,并映射到真正的renderer,而放在原位的是placeholder renderer。
WebKit 使用一个标记来表示是否全部的顶级样式表(包括 @imports)均已加载完毕。若是在attaching(DOM+CSSOM --> Render tree)过程当中样式还没有彻底加载,则使用占位符,并在文档中进行标注,等样式表加载完毕后再从新计算。
renderer在建立完成并添加到render tree时,并不包含 位置和大小 信息。计算这些值的过程称为布局或重排(Layout/Reflow)。
HTML 采用基于流的布局模型,这意味着大多数状况下只要一次遍历就能计算出几何信息。处于流中靠后位置元素一般不会影响靠前位置元素的几何特征,所以布局能够按从左至右、从上至下的顺序遍历文档。
为避免对全部细小更改都进行总体布局,浏览器采用了一种“dirty 位”系统。若是renderer有更改,或者其自身及其children被标注为“dirty”——则须要进行布局。
有两种标记:“dirty”和“children are dirty”。“children are dirty”表示renderer自身没有变化,但它的children须要布局。
全局布局是指触发了整个render tree的布局,触发缘由可能包括:
布局能够采用增量方式,也就是只对 dirty 的 renderer 进行布局(这样可能存在须要进行额外布局的弊端)。
当renderer为 dirty 时,触发增量布局(异步)。例如,当来自网络的额外内容添加到 DOM 树以后,新的renderer附加到了render tree中。
增量布局是异步执行的。
请求样式信息(如“offsetHeight”)的脚本可触发同步增量布局。
全局布局每每是同步执行的。
有时,当初始布局完成以后,若是一些属性(如滚动位置)发生变化,布局就会做为回调而触发。
布局过程一般以下:
父renderer肯定本身的宽度。
父renderer依次处理子renderer,而且:
父renderer根据子renderer的累加高度以及边距和补白的高度来设置自身高度,此值也可供父renderer的父renderer使用。
将其 dirty 位设置为 false。
renderer宽度是根据容器块(container block)的宽度、renderer样式中的“width”属性以及边距和边框计算得出的。
若是renderer在布局过程当中须要换行,会当即中止布局,并告知其父renderer须要换行。父renderer会建立额外的renderer,并对其调用布局。
在绘制阶段,会遍历render tree,并调用renderer的“paint”方法,将renderer的内容显示在屏幕上。绘制工做是使用用户界面基础组件(UI infrastructure component)完成的。
和布局同样,绘制也分为全局(绘制整个render tree)和增量两种。在增量绘制中,部分renderer发生了更改,可是不会影响整个树。更改后的renderer将其在屏幕上对应的矩形区域设为无效,这致使 OS 将其视为一块“dirty 区域”,并生成“paint”事件。OS 会很巧妙地将多个区域合并成一个。
CSS2 defines the order of the painting process. This is actually the order in which the elements are stacked in the stacking contexts. This order affects painting since the stacks are painted from back to front.
block renderer的堆栈顺序是:
在发生变化时,浏览器会尽量作出最小的响应。好比元素的颜色改变后,只会对该元素进行重绘。元素的位置改变后,只会对该元素及其子元素(可能还有同级元素)进行布局和重绘。添加 DOM 节点后,会对该节点进行布局和重绘。
一些重大变化(例如增大“html”元素的字体)会致使缓存无效,使得整个render tree都会进行从新布局和绘制。
结合整个render tree构建和lauout,paint阶段,能够去思考怎么减小relayout/repaint。
渲染引擎是单线程的。几乎全部操做(除了网络操做)都是在单线程中进行的。在 Firefox 和 Safari 中,该线程就是浏览器的主线程。而在 Chrome 浏览器中,该线程是tab进程的主线程。
网络操做可由多个线程并行执行。并行链接数是有限的(一般为 2~6 个)。
The browser main thread is an event loop. It's an infinite loop that keeps the process alive. It waits for events (like layout and paint events) and processes them.
这里可配合 #21 阅读,结合上面一小段,可展开讨论下。
在浏览器的具体实现里,浏览器内核(渲染进程)是多线程的。其中最重要的线程有:
GUI线程,即本章所讲的渲染引擎线程,负责解析HTML/CSS,构建DOM tree和 render tree,布局和绘制等。
页面第一次展现,或者须要重绘(repaint)或因为某种操做引起回流(reflow)时,该线程运行。
JS线程,即JS引擎线程,负责解析JavaScript脚本,运行代码。JS引擎一直等待着任务队列中任务的到来,而后执行。
一个Tab页(渲染进程)中不管何时都只有一个JS线程在运行——JS是单线程的。
其它线程。
GUI线程和JS线程是互斥的(由于JavaScript可操纵DOM)。这就是为何JS长时间运行会致使浏览器失去响应。
加微信:boan910227,备注:大前端;进前端进阶群;