上一篇文章主要讲了“从URL输入到页面展示到底发生了什么”,而这篇文章主要针对第五步“浏览器解析渲染页面”作更详细的理解~浏览器
例如:在浏览器输入https://juejin.im,而后通过 DNS 解析,juejin.im对应的 IP 是36.248.217.149(不一样时间、地点对应的 IP 可能会不一样)。而后浏览器向该 IP 发送 HTTP 请求。 服务端接收到 HTTP 请求,而后通过计算(向不一样的用户推送不一样的内容),返回 HTTP 请求,返回的内容以下: 性能优化
![]()
事实上,构建DOM的过程当中,不是等全部Token都转换完成后再去生成节点对象,而是一边生成Token一边消耗Token来生成节点对象。换句话说,每一个Token被生成后,会马上消耗这个Token建立出节点对象。注意:带有结束标签标识的Token不会立刻建立节点对象。bash
在这一过程当中,浏览器会肯定下每个节点的样式究竟是什么,而且这一过程实际上是很消耗资源的。由于样式你能够自行设置给某个节点,也能够经过继承得到。在这一过程当中,浏览器得递归 CSSOM 树,而后肯定具体的元素究竟是什么样式。 注意:CSS匹配HTML元素是一个至关复杂和有性能问题的事情。因此,DOM树要小,CSS尽可能用id和class,千万不要过渡层叠下去。服务器
当咱们生成 DOM 树和 CSSOM 树之后,就须要将这两棵树组合为渲染树。渲染树只会包括须要显示的节点和这些节点的样式信息,若是某个节点是 display: none 的,那么就不会在渲染树中显示。 网络
提问1:浏览器若是渲染过程当中遇到JS文件怎么处理?异步
渲染过程当中,若是遇到 < script > 就中止渲染,执行 JS 代码。由于浏览器有GUI渲染线程与JS引擎线程,为了防止渲染出现不可预期的结果,这两个线程是互斥的关系。JavaScript的加载、解析与执行会阻塞DOM的构建,也就是说,在构建DOM时,HTML解析器若遇到了JavaScript,那么它会暂停构建DOM,将控制权移交给JavaScript引擎,等JavaScript引擎运行完毕,浏览器再从中断的地方恢复DOM构建。async
也就是说,若是你想首屏渲染的越快,就越不该该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的缘由。固然在当下,并非说 script 标签必须放在底部,由于你能够给 script 标签添加 defer 或者 async 属性(下文会介绍这二者的区别)。ide
提问2: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。
注意:回流和重绘:1.计算CSS样式 2.构建Render Tree 3.Layout – 定位坐标和大小 4.正式开画
其中蓝色线表明JavaScript加载;红色线表明JavaScript执行;绿色线表明 HTML 解析。
<script src="script.js"></script>
没有 defer 或 async,浏览器会当即加载并执行指定的脚本,也就是说不等待后续载入的文档元素,读到就加载并执行。
<script async src="script.js"></script> (异步下载)
async 属性表示异步执行引入的 JavaScript,与 defer 的区别在于,若是已经加载好,就会开始执行——不管此刻是 HTML 解析阶段仍是 DOMContentLoaded 触发以后。须要注意的是,这种方式加载的 JavaScript 依然会阻塞 load 事件。换句话说,async-script 可能在 DOMContentLoaded 触发以前或以后执行,但必定在 load 触发以前执行。
<script defer src="script.js"></script>(延迟执行)
defer 属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未中止解析,这两个过程是并行的。整个 document 解析完毕且 defer-script 也加载完成以后(这两件事情的顺序无关),会执行全部由 defer-script 加载的 JavaScript 代码,而后触发 DOMContentLoaded 事件。
defer 与相比普通 script,有两点区别:
Javascript动态修改了DOM属性或是CSS属性会致使从新Layout,但有些改变不会从新Layout,就是上图中那些指到天上的箭头,好比修改后的CSS rule没有被匹配到元素。 这里重要要说两个概念,一个是Reflow,另外一个是Repaint
当咱们对 DOM 的修改致使了样式的变化、却并未影响其几何属性(好比修改了颜色或背景色)时,浏览器不需从新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。
当咱们对 DOM 的修改引起了 DOM 几何尺寸的变化(好比修改元素的宽、高或隐藏元素等)时,浏览器须要从新计算元素的几何属性(其余元素的几何属性和位置也会所以受到影响),而后再将计算的结果绘制出来。这个过程就是回流(也叫重排)
咱们知道,当网页生成的时候,至少会渲染一次。在用户访问的过程当中,还会不断从新渲染。从新渲染会重复回流+重绘或者只有重绘。
注:回流一定会发生重绘,重绘不必定会引起回流
重绘和回流会在咱们设置节点样式时频繁出现,同时也会很大程度上影响性能。回流所需的成本比重绘高的多,改变父节点里的子节点极可能会致使父节点的一系列回流。
任何会改变元素几何信息(元素的位置和尺寸大小)的操做,都会触发回流。
使用 transform 替代 top
使用 visibility 替换 display: none ,由于前者只会引发重绘,后者会引起回流(改变了布局)
不要把节点的属性值放在一个循环里当成循环里的变量。
for(let i = 0; i < 1000; i++) {
// 获取 offsetTop 会致使回流,由于须要去获取正确的值
console.log(document.querySelector('.test').style.offsetTop)
}
复制代码
不要使用 table 布局,可能很小的一个小改动会形成整个 table 的从新布局
动画实现的速度的选择,动画速度越快,回流次数越多,也能够选择使用 requestAnimationFrame
CSS 选择符从右往左匹配查找,避免节点层级过多
将频繁重绘或者回流的节点设置为图层,图层可以阻止该节点的渲染行为影响别的节点。好比对于 video 标签来讲,浏览器会自动将该节点变为图层。
基于上面介绍的浏览器渲染原理,DOM 和 CSSOM 结构构建顺序,初始化能够对页面渲染作些优化,提高页面性能。
defer属性: 用于开启新的线程下载脚本文件,并使脚本在文档解析完成后执行。
async属性: HTML5新增属性,用于异步下载脚本文件,下载完毕当即解释执行代码。