浏览器的内核是指支持浏览器运行的最核心的程序,分为两个部分的,一是渲染引擎,另外一个是JS引擎。渲染引擎在不一样的浏览器中也不是都相同的。好比在 Firefox 中叫作 Gecko,在 Chrome 和 Safari 中都是基于 WebKit 开发的。本文咱们主要介绍关于 WebKit 的这部分渲染引擎内容以及几个相关的问题。html
如需获取思惟导图或想阅读更多优质文章请猛戳GitHub博客 node
浏览器工做流程大致分为以下三部分:git
接下来咱们针对这其中所经历的重要步骤,一一详细阐述。github
浏览器会遵照一套步骤将HTML 文件转换为 DOM 树。宏观上,能够分为几个步骤:chrome
在网络中传输的内容其实都是 0 和 1 这些字节数据。当浏览器接收到这些字节数据之后,它会将这些字节数据转换为字符串,也就是咱们写的代码。浏览器
<html>
、<body>
等。Token中会标识出当前Token是“开始标签”或是“结束标签”亦或是“文本”等信息。这时候你必定会有疑问,节点与节点之间的关系如何维护?bash
事实上,这就是Token要标识“起始标签”和“结束标签”等标识的做用。例如“title”Token的起始标签和结束标签之间的节点确定是属于“head”的子节点。 网络
上图给出了节点之间的关系,例如:“Hello”Token位于“title”开始标签与“title”结束标签之间,代表“Hello”Token是“title”Token的子节点。同理“title”Token是“head”Token的子节点。app
事实上,构建DOM的过程当中,不是等全部Token都转换完成后再去生成节点对象,而是一边生成Token一边消耗Token来生成节点对象。换句话说,每一个Token被生成后,会马上消耗这个Token建立出节点对象。注意:带有结束标签标识的Token不会建立节点对象。异步
接下来咱们举个例子,假设有段HTML文本:
<html>
<head>
<title>Web page parsing</title>
</head>
<body>
<div>
<h1>Web page parsing</h1>
<p>This is an example Web page.</p>
</div>
</body>
</html>
复制代码复制代码
上面这段HTML会解析成这样:
DOM会捕获页面的内容,但浏览器还须要知道页面如何展现,因此须要构建CSSOM。
构建CSSOM的过程与构建DOM的过程很是类似,当浏览器接收到一段CSS,浏览器首先要作的是识别出Token,而后构建节点并生成CSSOM。
注意:CSS匹配HTML元素是一个至关复杂和有性能问题的事情。因此,DOM树要小,CSS尽可能用id和class,千万不要过渡层叠下去。
当咱们生成 DOM 树和 CSSOM 树之后,就须要将这两棵树组合为渲染树。
在这一过程当中,不是简单的将二者合并就好了。渲染树只会包括须要显示的节点和这些节点的样式信息,若是某个节点是 display: none
的,那么就不会在渲染树中显示。
当浏览器生成渲染树之后,就会根据渲染树来进行布局(也能够叫作回流)。这一阶段浏览器要作的事情是要弄清楚各个节点在页面中的确切位置和大小。一般这一行为也被称为“自动重排”。
布局流程的输出是一个“盒模型”,它会精确地捕获每一个元素在视口内的确切位置和尺寸,全部相对测量值都将转换为屏幕上的绝对像素。
布局完成后,浏览器会当即发出“Paint Setup”和“Paint”事件,将渲染树转换成屏幕上的像素。
以上咱们详细介绍了浏览器工做流程中的重要步骤,接下来咱们讨论几个相关的问题:
JavaScript的加载、解析与执行会阻塞DOM的构建,也就是说,在构建DOM时,HTML解析器若遇到了JavaScript,那么它会暂停构建DOM,将控制权移交给JavaScript引擎,等JavaScript引擎运行完毕,浏览器再从中断的地方恢复DOM构建。
也就是说,若是你想首屏渲染的越快,就越不该该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的缘由。固然在当下,并非说 script 标签必须放在底部,由于你能够给 script 标签添加 defer 或者 async 属性(下文会介绍这二者的区别)。
JS文件不仅是阻塞DOM的构建,它会致使CSSOM也阻塞DOM的构建。
本来DOM和CSSOM的构建是互不影响,井水不犯河水,可是一旦引入了JavaScript,CSSOM也开始阻塞DOM的构建,只有CSSOM构建完毕后,DOM再恢复DOM构建。
这是什么状况?
这是由于JavaScript不仅是能够改DOM,它还能够更改样式,也就是它能够更改CSSOM。前面咱们介绍,不完整的CSSOM是没法使用的,但JavaScript中想访问CSSOM并更改它,那么在执行JavaScript时,必需要能拿到完整的CSSOM。因此就致使了一个现象,若是浏览器还没有完成CSSOM的下载和构建,而咱们却想在此时运行脚本,那么浏览器将延迟脚本执行和DOM构建,直至其完成CSSOM的下载和构建。也就是说,在这种状况下,浏览器会先下载和构建CSSOM,而后再执行JavaScript,最后在继续构建DOM。
咱们知道,当网页生成的时候,至少会渲染一次。在用户访问的过程当中,还会不断从新渲染。从新渲染会重复上图中的第四步(回流)+第五步(重绘)或者只有第五个步(重绘)。
回流一定会发生重绘,重绘不必定会引起回流。重绘和回流会在咱们设置节点样式时频繁出现,同时也会很大程度上影响性能。回流所需的成本比重绘高的多,改变父节点里的子节点极可能会致使父节点的一系列回流。
任何会改变元素几何信息(元素的位置和尺寸大小)的操做,都会触发回流,
下面例子中,触发了几回回流和重绘?
var s = document.body.style;
s.padding = "2px"; // 回流+重绘
s.border = "1px solid red"; // 再一次 回流+重绘
s.color = "blue"; // 再一次重绘
s.backgroundColor = "#ccc"; // 再一次 重绘
s.fontSize = "14px"; // 再一次 回流+重绘
// 添加node,再一次 回流+重绘
document.body.appendChild(document.createTextNode('abc!'));
复制代码复制代码
for(let i = 0; i < 1000; i++) {
// 获取 offsetTop 会致使回流,由于须要去获取正确的值
console.log(document.querySelector('.test').style.offsetTop)
}
复制代码复制代码
接下来咱们对比下 defer 和 async 属性的区别:
其中蓝色线表明JavaScript加载;红色线表明JavaScript执行;绿色线表明 HTML 解析。
没有 defer 或 async,浏览器会当即加载并执行指定的脚本,也就是说不等待后续载入的文档元素,读到就加载并执行。
async 属性表示异步执行引入的 JavaScript,与 defer 的区别在于,若是已经加载好,就会开始执行——不管此刻是 HTML 解析阶段仍是 DOMContentLoaded 触发以后。须要注意的是,这种方式加载的 JavaScript 依然会阻塞 load 事件。换句话说,async-script 可能在 DOMContentLoaded 触发以前或以后执行,但必定在 load 触发以前执行。
defer 属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未中止解析,这两个过程是并行的。整个 document 解析完毕且 defer-script 也加载完成以后(这两件事情的顺序无关),会执行全部由 defer-script 加载的 JavaScript 代码,而后触发 DOMContentLoaded 事件。
defer 与相比普通 script,有两点区别:载入 JavaScript 文件时不阻塞 HTML 的解析,执行阶段被放到 HTML 标签解析完成以后。 在加载多个JS脚本的时候,async是无顺序的加载,而defer是有顺序的加载。
由于 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当咱们经过 JS 操做 DOM 的时候,其实这个操做涉及到了两个线程之间的通讯,那么势必会带来一些性能上的损耗。操做 DOM 次数一多,也就等同于一直在进行线程之间的通讯,而且操做 DOM 可能还会带来重绘回流的状况,因此也就致使了性能上的问题。
因为浏览器的渲染机制不一样,在渲染页面时会出现两种常见的不良现象----白屏问题和FOUS(无样式内容闪烁)
FOUC:因为浏览器渲染机制(好比firefox),再CSS加载以前,先呈现了HTML,就会致使展现出无样式内容,而后样式忽然呈现的现象;
白屏:有些浏览器渲染机制(好比chrome)要先构建DOM树和CSSOM树,构建完成后再进行渲染,若是CSS部分放在HTML尾部,因为CSS未加载完成,浏览器迟迟未渲染,从而致使白屏;也多是把js文件放在头部,脚本会阻塞后面内容的呈现,脚本会阻塞其后组件的下载,出现白屏问题。
浏览器工做流程:构建DOM -> 构建CSSOM -> 构建渲染树 -> 布局 -> 绘制。
CSSOM会阻塞渲染,只有当CSSOM构建完毕后才会进入下一个阶段构建渲染树。
一般状况下DOM和CSSOM是并行构建的,可是当浏览器遇到一个script标签时,DOM构建将暂停,直至脚本完成执行。但因为JavaScript能够修改CSSOM,因此须要等CSSOM构建完毕后再执行JS。
若是你想首屏渲染的越快,就越不该该在首屏就加载 JS 文件,建议将 script 标签放在 body 标签底部。