1. 浏览器的内核(渲染引擎)
早期的ie内核,Trident,一直沿用到ie11,ie11是
Gecko内核,firfox的内核,js引擎是JaegerMonkey
Blink内核,Chrome,opera等浏览器使用这个内核,Chrome的js引擎是v8
webkit内核,在移动设备端应用很广泛,使用的是webcore排版引擎,webcore是基于khtml的排版引擎
这些内核主要是渲染html和css的,js的话有单独的引擎。
2. 浏览器的渲染过程
http://www.cnblogs.com/cnwebdeveloper/articles/2234423.html
解析html以构建dom树->构建render树->布局render树->绘制render树
2.1 渲染引擎开始解析html,并将标签转化为内容树的dom节点,并组成dom树,也就是解析树,dom是文档对象模型的缩写,它是html文档的对象表示,作为html元素的外部接口,供js等调用。生成dom树的时候会执行document.write
解析算法 The parsing algorithm
正如前面章节中讨论的,html不能被一般的自顶向下或自底向上的解析器所解析。
原因是:
1. 这门语言本身的宽容特性
2. 浏览器对一些常见的非法html有容错机制
3. 解析过程是往复的,通常源码不会在解析过程中发生改变,但在html中,脚本标签包含的“document.write”可能添加标签,这说明在解析过程中实际上修改了输入
不能使用正则解析技术,浏览器为html定制了专属的解析器。
解析树的生成,分成两个阶段:符号化以及构建树
符号化是词法分析的过程,将输入解析为符号,html的符号包括开始标签、结束标签、属性名及属性值。
符号识别器识别出符号后,将其传递给树构建器,并读取下一个字符,以识别下一个符号,这样直到处理完所有输入。
图9:HTML解析流程
符号识别算法 The tokenization algorithm
树的构建算法 Tree construction algorithm
解析结束时的处理 Action when the parsing is finished
浏览器容错 Browsers error tolerance
在dom生成后会触发load事件。
2.2 接着,它解析外部css文件及style标签中的样式信息,这些样式信息以及html的可见性指令将被用来构建另一棵树,render树
render树由一些包含颜色和大小等属性的矩形组成,它们将被按照正确的顺序显示到屏幕上。
渲染(render)树和dom树的关系:渲染对象和dom元素相对应,但这种对应关系不是一对一的,不可见的dom元素不会被插入渲染树,例如head元素,另外display为none的元素也不会在渲染树中出现(visibility属性为hidden的元素将出现在渲染树中)
还有一些Dom元素对应几个可见对象,它们一般是一些具有复杂结构的元素,无法用一个矩形来描述。例如,select元素有三个渲染对象——一个显示区域、一个下拉列表及一个按钮。同样,当文本因为宽度不够而折行时,新行将作为额外的渲染元素被添加。另一个多个渲染对象的例子是不规范的html,根据css规范,一个行内元素只能仅包含行内元素或仅包含块状元素,在存在混合内容时,将会创建匿名的块状渲染对象包裹住行内元素。
一些渲染对象和所对应的Dom节点不在树上相同的位置,例如,浮动和绝对定位的元素在文本流之外,在两棵树上的位置不同,渲染树上标识出真实的结构,并用一个占位结构标识出它们原来的位置。
图13:渲染树及对应的Dom树
这一步包含css解析,css属于上下文无关文法。
Webkit使用Flex和Bison解析生成器从CSS语法文件中自动生成解析器。回忆一下解析器的介绍,Bison创建一个自底向上的解析器,flex使用自顶向下解析器。它们都是将每个css文件解析为样式表对象,每个对象包含css规则,css规则对象包含选择器和声明对象,以及其他一些符合css语法的对象。
css解析的过程很复杂,会先生成样式表,然后根据规则和render树对应,并计算样式。不同浏览器的实现方式会略有不同,详细参考上面的链接。
2.3 render树构建好之后,将会执行布局过程,它将确定每个节点在屏幕上的确切坐标。
当渲染对象被创建并添加到数中,它们并没有位置和大小,计算这些值的过程成为layout或reflow
html使用基于流的布局模型,意味着大部分时间,可以以单一的途径进行几何计算。流中靠后的元素并不会影响前面元素的几何特性,所以布局可以在文档中从右向左、自上而下的进行。也存在一些例外,比如html tables
dirty bit系统
为了不因为每个小变化都全部重新布局,浏览器使用一个dirty bit系统,一个渲染对象发生了变化或是被添加了,就标记它及它的children为dirty-需要layout。存在两个标识-dirty及children are dirty,children are dirty说明即使这个渲染对象可能没问题,但它至少有一个child需要layout。
全局和增量layout,异步和同步layout
一个全局的样式改变影响所有的渲染对象,比如字号的修改,窗口resize,是同步触发
增量layout是异步的,另外当脚本请求样式信息的时候,例如offsetHeight,会同步触发增量布局。
有时候,layout会被作为一个初始layout之后的回调,比如滑动条的滑动。
当一个渲染对象在布局过程中需要折行时,则暂停并告诉它的parent它需要折行,parent将创建额外的渲染对象并调用它们的layout。
优化:
使用css3的动画修改,改变渲染位置(并不是改变大小)时,渲染对象的大小将会从缓存中读取,而不会重新就算。也就是不会重排,但是会重绘,但是重绘会在GPU中渲染,所以不会消耗太多性能。
2.4 再下一步就是绘制,即遍历render树,并使用ui后端层绘制每个节点。
绘制阶段,遍历渲染树并调用渲染对象的paint方法将它们的内容显示在屏幕上,绘制使用UI基础组件
和布局一样,绘制也可以是全局的-绘制完整的树-或增量的。在增量的绘制过程中,一些渲染对象以不影响整棵树的方式改变,改变的渲染对象使其在屏幕上的矩形区域失效,这将导致操作系统将其看作dirty区域,并产生一个paint事件,操作系统很巧妙的处理这个过程,并将多个区域合并为一个。Chrome中,这个过程更复杂些,因为渲染对象在不同的进程中,而不是在主进程中。Chrome在一定程度上模拟操作系统的行为,表现为监听事件并派发消息给渲染根,在树中查找到相关的渲染对象,重绘这个对象(往往还包括它的children)。
Firefox显示列表
Firefox读取渲染树并为绘制的矩形创建一个显示列表,该列表以正确的绘制顺序包含这个矩形相关的渲染对象。
用这样的方法,可以使重绘时只需查找一次树,而不需要多次查找——绘制所有的背景、所有的图片、所有的border等等。
Firefox优化了这个过程,它不添加会被隐藏的元素,比如元素完全在其他不透明元素下面。
Webkit矩形存储
重绘前,webkit将旧的矩形保存为位图,然后只绘制新旧矩形的差集。
动态变化
浏览器总是试着以最小的动作响应一个变化,所以一个元素颜色的变化将只导致该元素的重绘,元素位置的变化将大致元素的布局和重绘,添加一个Dom节点,也会大致这个元素的布局和重绘。一些主要的变化,比如增加html元素的字号,将会导致缓存失效,从而引起整数的布局和重绘。
渲染引擎的线程
渲染引擎是单线程的,除了网络操作以外,几乎所有的事情都在单一的线程中处理,在Firefox和Safari中,这是浏览器的主线程,Chrome中这是tab的主线程。
网络操作由几个并行线程执行,并行连接的个数是受限的(通常是2-6个)。
事件循环
浏览器主线程是一个事件循环,它被设计为无限循环以保持执行过程的可用,等待事件(例如layout和paint事件)并执行它们。下面是Firefox的主要事件循环代码。
2.5 脚本的解析过程
2.5.1 脚本
web的模式是同步的,开发者希望解析到一个script标签时立即解析执行脚本,并阻塞文档的解析知道脚本执行完。如果脚本是外引的,则网络必须先请求到这个资源,这个过程也是同步的,会阻塞文档的解析直到资源被请求到。这个模式保持了很多年,并且在html4和html5中都特别指定了。开发者可以将脚本标识为defer,以使其不阻塞文档解析,并在文档解析结束后执行。html5增加了标记脚本为异步的选项,以使脚本的解析执行使用另一个线程。
2.5.2 预解析
Webkit和Firefox都做了这个优化,当执行脚本时,另一个线程解析剩下的文档,并加载后面需要通过网络加载的资源。这种方式可以使资源并行加载从而使整体速度更快。需要注意的是,预解析并不改变Dom树,它将这个工作留给主解析过程,自己只解析外部资源的引用,比如外部脚本、样式表及图片。
2.5.3 样式表
样式表采用另一种不同的模式。理论上,既然样式表不改变Dom树,也就没有必要停下文档的解析等待它们,然而,存在一个问题,脚本可能在文档的解析过程中请求样式信息,如果样式还没有加载和解析,脚本将得到错误的值,显然这将会导致很多问题,这看起来是个边缘情况,但确实很常见。Firefox在存在样式表还在加载和解析时阻塞所有的脚本,而chrome只在当脚本试图访问某些可能被未加载的样式表所影响的特定的样式属性时才阻塞这些脚本。