网页生成的过程
一、浏览器渲染页面的流程
当浏览器获得一个html文件时,会 “自上而下” 加载,并在加载过程中进行解析渲染。
- 浏览器会将HTML解析成一个DOM树,DOM 树的构建过程是一个深度遍历过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。
- 将CSS代码转化成CSSOM(CSS Object Model)
- 根据DOM树和CSSOM来构造 Rendering Tree。注意:Rendering Tree 渲染树并不等同于 DOM 树,因为一些像 Header 或 display:none 的标签就没必要放在渲染树中了。
- 生成布局(layout),即将所有渲染树的所有节点进行平面合成。
- 将布局绘制(paint) 在屏幕上。
上面的步骤当中,1-3步都非常快,真正耗时的是4-5步。
render.png二、回流与重绘
上述这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局render树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。
网页生成的时候,至少会渲染一次。用户访问的过程中,还会不断重新渲染。以下四种情况,会导致网页重新渲染。
- 页面初始化
- 修改DOM
- 修改样式表
- 用户事件(比如鼠标悬停、页面滚动、输入框键入文字、改变窗口大小等等)
重新渲染,就需要重新生成布局和重新绘制。前者叫做"回流/重排"(reflow),后者叫做"重绘"(repaint)。
- 回流/重排(Reflow)
浏览器要花时间去渲染,当它发现了某个部分发生了变化影响了布局,那就需要倒回去重新渲染。"回流/重排"必然导致"重绘"。
- 重绘(Repaint)
如果只是改变了某个元素的背景颜色,文字颜色等,不影响元素周围或内部布局的属性,将只会引起浏览器的repaint,重画某一部分。
"重绘"不一定需要"重排"。但是,"重排"必然导致"重绘"。Reflow要比Repaint更花费时间,也就更影响性能。所以在写代码的时候,要尽量避免过多的Reflow。
三、对于性能的影响
提高网页性能,就是要降低"重排"和"重绘"的频率和成本,尽量少触发重新渲染。
-
优化CSS
CSS选择符是从右到左进行匹配的。从右到左!eg : #nav li 我们以为这是一条很简单的规则,秒秒钟就能匹配到想要的元素,但是,但是,但是,是从右往左匹配啊,所以,会去找所有的li,然后再去确定它的父元素是不是#nav。因此,写css的时候需要注意:
- 方法
- dom深度尽量浅。
- 避免后代选择符,尽量使用子选择符。原因:子元素匹配符的概率要大于后代元素匹配符。后代选择符;#tp p{} 子选择符:#tp>p{}
- 避免使用通配符,举一个例子,.mod .hd *{font-size:14px;} 根据匹配顺序,将首先匹配通配符,也就是说先匹配出通配符,然后匹配.hd(就是要对dom树上的所有节点进行遍历他的父级元素),然后匹配.mod,这样的性能耗费可想而知.
- 不要一条条地改变样式,而要通过改变class,或者csstext属性,一次性地改变样式。
- table元素的重排和重绘成本,要高于div元素
-
JavaScript的影响
- Javascript的加载和执行的特点
- 载入后马上执行;
- 执行时会阻塞页面后续的内容(包括页面的渲染、其它资源的下载)。因为浏览器需要一个稳定的DOM树结构,而JS中很有可能有代码直接改变了DOM树结构,比如使用 document.write 或 appendChild,甚至是直接使用的location.href进行跳转,因此,浏览器需要防止出现JS修改DOM树。
- 方法
- 将所有的script标签放到页面底部,也就是body闭合标签之前,这能确保在脚本执行前页面已经完成了DOM树渲染。
- 尽可能地合并脚本。页面中的script标签越少,加载也就越快,响应也越迅速。无论是外链脚本还是内嵌脚本都是如此。
- 采用无阻塞下载 JavaScript 脚本的方法:
(1)使用script标签的 defer 属性(仅适用于 IE 和 Firefox 3.5 以上版本);
(2)使用动态创建的script元素来下载并执行代码; - 尽量使用离线DOM,而不是真实的网面DOM,来改变元素样式。比如,操作Document Fragment对象,完成后再把这个对象加入DOM。再比如,使用 cloneNode() 方法,在克隆的节点上进行操作,然后再用克隆的节点替换原始节点。
- DOM 的多个读操作(或多个写操作),应该放在一起。不要两个读操作之间,加入一个写操作。
- 使用虚拟DOM的脚本库,比如Vue、React等。
四、HTML页面加载和解析流程举例
- 用户输入网址(假设是个html页面,并且是第一次访问),浏览器向服务器发出请求,服务器返回html文件;
- 浏览器开始载入html代码,发现<head>标签内有一个<link>标签引用外部CSS文件;
- 浏览器又发出CSS文件的请求,服务器返回这个CSS文件;
- 浏览器继续载入html中<body>部分的代码,并且CSS文件已经拿到手了,可以开始渲染页面了;
- 浏览器在代码中发现一个<img>标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续渲染后面的代码;
- 服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码;
- 浏览器发现了一个包含一行Javascript代码的<script>标签,赶快运行它;
- Javascript脚本执行了这条语句,它命令浏览器隐藏掉代码中的某个<div> (style.display=”none”)。突然少了这么一个元素,浏览器不得不重新渲染这部分代码;
- 终于等到了</html>的到来,浏览器泪流满面……
- 等等,还没完,用户点了一下界面中的“换肤”按钮,Javascript让浏览器换了一下<link>标签的CSS路径;
- 浏览器召集了在座的各位<div><span><ul><li>们,“大伙儿收拾收拾行李,咱得重新来过……”,浏览器向服务器请求了新的CSS文件,重新渲染页面。
网友评论