目录

浏览器的渲染原理

# 网页的解析过程

用户在浏览器输入域名 --> DNS服务器解析出具体IP --> 发送请求到对应的服务器 --> 服务器返回响应的静态资源 --> 客户端下载静态资源(index.html) --> 遇到link元素css文件 --> 下载css文件

​ --> 遇到script元素 --> 下载js文件

# 浏览器的渲染流程

# 浏览器内核

浏览器内核也称为排版引擎,网页通过排版引擎进行渲染

# 浏览器渲染页面的详细流程

image

# 解析一 HTML解析过程

默认情况下服务器会给浏览器返回index.html文件,所以解析HTML是所有步骤的开始;

解析HTML会构建DOM Tree

# 解析二 生成CSS规则

在解析的过程中,如果遇到CSS的link元素,那么会由浏览器负责下载对应的CSS文件(注意:下载CSS文件是不会影响DOM的解析的)

浏览器下载完CSS文件后,就会对CSS文件进行解析,解析出对应规则树,可以称之为CSSOM(CSS Object Model)

# 解析三 构建Render Tree

当有了DOM Tree和CSSOM Tree后就可以两个结合来构建Render Tree

注意

  • link元素不会阻塞DOM Tree的构建过程,但是会阻塞Render Tree的构建过程;这是因为Render Tree在构建时,需要对应的CSSOM Tree
  • Render Tree和DOM Tree并不是一一对应的关系,比如对于display为none的元素,压根不会出现在Render Tree中

# 解析四 布局(layout)和绘制(paint)

第四步是在渲染树上运行布局以计算每个节点的几何体

​ 渲染树会表示显示哪些节点以及其他样式,但是不表示每个节点的尺寸、位置等信息

​ 布局是确定呈现树中所有节点的宽度、高度和位置信息

第五步是将每个节点绘制到屏幕上

​ 在绘制阶段,浏览器将布局阶段计算的每个frame转为屏幕上实际的像素点

​ 包括将元素的可见部分进行绘制,比如文本、颜色、边框、阴影、替换元素

image

# 回流和重绘解析

# 回流(reflow)

  • 回流也称为重排
  • 第一次确定节点的大小和位置,称之为布局
  • 之后对节点的大小、位置修改重新计算称之为回流

# 引起回流的情况

  • DOM结构发生改变(添加新的节点或者移除节点)
  • 改变了布局(修改了width、height、padding、font-size等)
  • 修改了窗口resize(修改了窗口的尺寸等)
  • 调用getComputedStyle方法获取尺寸、位置信息

# 重绘(repaint)

  • 第一次渲染内容称之为绘制
  • 之后的重新渲染称之为重绘

# 引起重绘的情况

  • 修改背景色、文字颜色、边框颜色、样式等

# 总结

  • 回流一定会引起重绘,所以回流是一件很消耗性能的事情,因此在开发中要尽量避免发生回流
  • 修改样式时尽量一次性修改,比如通过cssText修改,比如通过class修改
  • 尽量避免频繁的操作DOM,可以在一个DocumentFragement或者父元素中将要操作的DOM完成,再一次性执行
  • 尽量避免使用getComputedStyle获取尺寸、位置等信息
  • 对某些元素使用position或者fixed,并不是不会引起回流,而是开销相对较小,不会对其它元素造成影响。

# 合成和性能优化

# 特殊解析 --- composite合成

绘制过程中,可以将布局后的元素绘制到多个合成图层中,这是浏览器的一种优化手段。

默认情况下,标准流中的内容都是被绘制在同一个图层中的。

一些特殊的属性会创建一个新的合成图层,并且新的图层可以利用GPU来加速绘制,因为每个合成图层都是单独渲染的

常见的可以形成新的合成层的属性

  • 3D transforms 必须是3D
  • video、canvas、iframe.
  • opacity 动画转换
  • position: fixed
  • will-change: 一个实验性的属性,提前告诉浏览器元素可能发生哪些变化
  • animation或transition设置了opacity、transform

分层确实可以提高性能,但是它是以内存管理为代价,因此不应作为web性能优化策略的一部分而过度使用

# defer和async属性

# script元素和页面解析的关系

# 页面渲染过程中,JavaScript如何执行

  • 浏览器解析HTML的过程中,遇到了script元素是不能继续构建DOM树的。
  • 它会停止继续构建,首先下载JavaScript代码,并且执行JavaScript的脚本
  • 只有等到JavaScript脚本执行结束后,才会继续解析HTML,构建DOM树

# 以这种形式渲染的原因

  • JavaScript的作用之一就是操作DOM,并且可能会修改DOM
  • 如果等到DOM树构建完成并且渲染再执行JavaScript,会造成严重的回流和重绘,影响页面性能
  • 在遇到script元素时,优先下载JavaScript代码,再继续构建DOM树

# 产生的新问题,特别是在现代页面开发中

  • 在目前的开发模式中(Vue, React),脚本往往比HTML页面更重,处理时间更长
  • 所以会造成页面的解析阻塞,在脚本下载、执行完成之前,用户在界面上什么都看不到。

为了解决上面的问题,script元素提供了两个属性(attribute) deferasync

# defer属性

defer属性告诉浏览器不要等待脚本下载,而是继续解析HTML,构建DOM Tree

  • 脚本会由浏览器来进行下载,但是不会阻塞DOM Tree的构建过程
  • 如果脚本已经提前下载好了,会等待 DOM Tree构建完成,即在执行defer中的代码时,DOM Tree已经构建完成了;在DOMContentLoaded事件之前先执行defer中的代码

因此DOMContentLoaded总是会等待defer中的代码先执行完成

<script src="./test.js" defer></script>
<script>
	window.addEventListener("DOMContentLoaded", () => {
        console.log("DOMContentLoaded")
    })
</script>
1
2
3
4
5
6

多个带defer属性的脚本是可以保持正确顺序执行的

从某种角度来说,defer属性可以提高页面的性能,并且推荐放到head 元素中

defer仅适用于外部脚本,对于script默认内容会被忽略

# async属性

async属性和defer有些类似,也能够让脚本不阻塞页面渲染。

async是让一个脚本完全独立的

  • 浏览器不会因async脚本而阻塞(同defer类似)

  • async脚本不能保证顺序,它是独立下载、独立运单,不会等待其它脚本

  • async不会能保证在DOMContentLoaded之前或之后执行

    <script>
    	window.addEventListener('DOMContentLoaded', () => {
            console.log('DOMContentLoaded')
        })
    </script>
    <script async src="/test.js"></script>
    
    1
    2
    3
    4
    5
    6
  • defer属性通常用于需要在文档脚本解析后操作DOM的JavaScript代码,并且对多个script文件有顺序要求

  • async通常用于独立的脚本,对其他脚本甚至DOM是没有依赖的。

上次更新: 2022/10/11, 08:38:56
最近更新
01
防抖和节流
02-06
02
正则表达式
01-29
03
async_await函数
12-30
更多文章>