浏览器内核是浏览器运行的最核心部分,一般分为两个部分,一个是渲染引擎,另一个是JavaScript引擎。
目前市面上常见的浏览器内核可以分为这四种:Trident(IE)、Gecko(火狐)、Blink(Chrome、Opera)、Webkit(Safari)。
本文我们就以 Webkit 为例,对现代浏览器的渲染过程进行一个深度的剖析。
我们首先简单介绍一下页面的加载过程
大致如下:
如果我们请求的是一个页面,返回的就是一堆HTML格式的字符串。
浏览器渲染大致分为三个过程
稍后我们重点讲这里
**DOM (Document Object Model): ** 文档对象模型。基于 DOM 表示的文档被描述成一个树形结构,DOM 提供了接口让 JavaScript 修改 HTML 文档。
浏览器会遵守一套步骤将HTML 文件转换为 DOM 树。宏观上,可以分为几个步骤:
、
等。Token中会标识出当前Token是“开始标签”或是“结束标签”亦或是“文本”等信息。<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Pathtitle>
head>
<body>
<p>Hello <span>web performancespan> students!p>
<div><img src="awesome-photo.jpg">div>
body>
html>
CSSOM(CSS Object Model): CSS 对象模型。CSSOM 定义了 JavaScript 访问样式的能力和方式。CSSOM 提供了接口让 JavaScript 获得和修改 CSS 代码设置的样式信息。
DOM会捕获页面的内容,但浏览器还需要知道页面如何展示,所以需要构建CSSOM。
与处理 HTML 时类似,需要将收到的 CSS 规则转换成某种浏览器能够理解和处理的内部表示。因此会重复构建 HTML 过程,不过是对 CSS文件 。
在这一过程中,浏览器会确定下每一个节点的样式到底是什么,并且这一过程其实是很消耗资源的。因为样式你可以自行设置给某个节点,也可以通过继承获得。在这一过程中,浏览器得递归 CSSOM 树,然后确定具体的元素到底是什么样式。
注意:CSS匹配HTML元素是一个相当复杂和有性能问题的事情。所以,DOM树要小,CSS尽量用id和class,千万不要过渡层叠下去。
当我们生成 DOM 树和 CSSOM 树以后,就需要将这两棵树组合为渲染树。
渲染树(RenderObject tree)是基于 DOM tree 建立起来的一棵新树。
渲染树只会包括需要显示的节点和这些节点的样式信息,如果某个节点是 display:none 的,那么就不会在渲染树中显示。
浏览器如果渲染过程中遇到JS文件怎么处理???
渲染过程中,如果遇到
没有 defer 或 async,浏览器会立即加载并执行指定的脚本,也就是说不等待后续载入的文档元素,读到就加载并执行。
defer 属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未停止解析,这两个过程是并行的。整个 document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载的 JavaScript 代码,然后触发 DOMContentLoaded 事件。
defer 与相比普通 script,有两点区别:载入 JavaScript 文件时不阻塞 HTML 的解析,执行阶段被放到 HTML 标签解析完成之后;在加载多个JS脚本的时候,async是无顺序的加载,而defer是有顺序的加载。
把 DOM 和 JavaScript 各自想象成一个岛屿,它们之间用收费桥梁连接。——《高性能 JavaScript》
JS 是很快的,在 JS 中修改 DOM 对象也是很快的。在JS的世界里,一切是简单的、迅速的。但 DOM 操作并非 JS 一个人的独舞,而是两个模块之间的协作。
因为 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们用 JS 去操作 DOM 时,本质上是 JS 引擎和渲染引擎之间进行了“跨界交流”。这个“跨界交流”的实现并不简单,它依赖了桥接接口作为“桥梁”(如下图)。
过“桥”要收费——这个开销本身就是不可忽略的。我们每操作一次 DOM(不管是为了修改还是仅仅为了访问其值),都要过一次“桥”。过“桥”的次数一多,就会产生比较明显的性能问题。因此“减少 DOM 操作”的建议,并非空穴来风。
渲染的流程基本上如下:1.计算CSS样式 ;2.构建Render Tree ;3.Layout(定位坐标和大小); 4.绘图(Pairt); 5.合成(composite)
了解两个概念,一个是Reflow,另一个是Repaint。
- 重绘:当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式。
- 回流:当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)
网页加载后,绘制新的每一帧,一般都需要经过计算布局(layout)、绘图(paint)、合成(composite)三阶段。
而在这三个阶段中,layout 和 paint 比较耗时间,而合成需要的时间相对较少一些。
回流(reLayout)
如果修改 DOM 元素的 layout 样式(如 width, heihgt 等),浏览器会计算页面需要 回流 的元素,然后触发一个 reLayout。被 reLayout的元素,接着会执行绘制,最后进行渲染合并生成页面。
重绘(rePaint)
如果修改 DOM 元素的 paint 样式(如 color, background 等),浏览器会跳过布局,直接执行绘制,再进行合成。
合成(composite)
如果修改 DOM 元素的 composite 样式(如 transform, opacity 等)。浏览器会跳过布局和绘制,直接执行合成。该过程是开销最小的,也是优化着手点。
重绘和回流会在我们设置节点样式时频繁出现,同时也会很大程度上影响性能。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。
如果想知道修改任何指定 CSS 样式会触发 layout、paint、composite 中的哪一个,请查看CSS 触发器。
任何会改变元素几何信息(元素的位置和尺寸大小)的操作,都会触发回流:
for(var i = 0; i < 100; i++){
//offsetTop会导致回流,因为要去获取值。
console.log(document.querySelector('.name').style.offsetTop);
}
前端工匠
singsong