目录
一个网页从输入URL
地址到显示是一个怎样的过程?
- 输入地址后,地址通过
DNS
服务器进行域名解析出对应的ip
地址 - 从
ip
地址对应的主机发送http
请求获取对应的静态资源 - 默认情况下服务器会返回
index.html
文件
index.html
是怎么被解析渲染的?
一个网页下载下来后由渲染引擎进行解析,下面内容都是由浏览器内核(比如苹果的webkit
内核的WebCore
部分和谷歌的blink
)进行解析渲染的,详细图解如下:
1. HTML解析
浏览器内核解析HTML
会构建DOM Tree
2. 生成CSS规则
- 在解析过程中当遇到
link
元素引入CSS
文件时,浏览器会下载CSS
文件 - 下载
CSS
文件不会影响HTML
的解析,是和DOM Tree
的生成同时进行的 - 下完后会对
CSS
文件进行解析,解析出对应的规则树,即CSSOM
(CSS Object Model
CSS对象模型)
3. 构建Render Tree
DOM Tree
和CSSOM Tree
结合构建Render Tree
- 因此
link
元素不会阻塞DOM Tree
的构建,但是会阻塞Render Tree
的构建 Render Tree
和DOM Tree
并不是一一对应的,比如DOM Tree
中设置display:none
的元素就不会出现在Render Tree
中Render Tree
仅包含需要显示的可见元素,并应用了CSS
样式,但是不表示每个节点的尺寸、位置等信息
4. 布局(Layout
)
布局生成渲染树中所有节点的宽度、高度和位置信息,结果存储在一个或多个框架或盒子对象中
回流
第一次确定节点的大小位置称为布局(Layout
) ,之后对节点大小位置改变后的重新计算称为回流(reflow
),也可称重排
什么情况下会引起回流?
总结一句话就是,只要操作会改变元素大小和布局都会引起回流,回流一定会引起重绘
DOM
结构发生改变(添加新的节点或者移除节点)- 改变布局(修改了
width、height、padding、font-size
等值) - 修改浏览器窗口的尺寸
- 调用
getComputedStyle
获取大小位置信息本身不会直接引起回流,但为了返回准确的样式信息,有的浏览器可能会执行回流以确保数据的准确性
如何减少回流?
- 修改样式时尽量一次性修改,比如通过
cssText
和添加class
修改 - 尽量避免频繁的操作
DOM
,可以在一个DocumentFragment
或者父元素中将要操作的DOM
操作完成,再一次性的操作 - 对于频繁变化的元素,使用
position: absolute
或position: fixed
以避免影响其他元素的布局 - 尽量避免频繁读取会导致回流的属性
5. 绘制(Paint
)
在绘制阶段,浏览器将布局阶段计算的每个frame
(也称为渲染对象或渲染盒子)转为屏幕上实际的像素点,包括将每个元素的视觉特性(颜色、边框、阴影等)绘制到多个图层上
重绘
第一次对元素的颜色背景阴影等的渲染叫绘制,之后当元素背景色、文字颜色、边框颜色、样式外观等属性发生变化时的再次渲染叫重绘
6. 合成(composite
)
- 将图层组合成最终的屏幕图像
- 绘制过程中会绘制到多个合成图层中,这是浏览器的一种优化手段
- 默认情况下,标准流中的内容都是被绘制在同一个图层(
Layer
)中的 - 一些特殊的属性会创建一个新的合成层(
CompositingLayer
),并且新的图层可以利用GPU
来加速绘制,因为每个合成层都是单独渲染的 - 分层确实可以提高性能,但是它以内存管理为代价,因此不应作为
web
性能优化策略的一部分过度使用
常见的一些特殊的属性如下:
调试时可以在浏览器更多工具中的layer
里看到以下属性是否会生成新的图层
3D transforms
video、canvas、iframe
opacity
动画转换时;position: fixed
will-change
:一个实验性的属性,提前告诉浏览器元素可能发生哪些变化;animation
或transition
设置了opacity、transform
JavaScript
脚本和页面解析的关系
- 浏览器在解析
HTML
的过程中,遇到script
元素会停止构建DOM Tree
- 然后会下载
JavaScript
代码并执行JavaScript
脚本 - 等
JavaScript
脚本执行完后才会继续解析HTML
,构建DOM Tree
- 这是因为
JavaScript
作用之一就是操作和修改DOM
,若等DOM Tree
构建和渲染之后再执行JavaScript
,会造成严重的回流和重绘,影响页面性能
若想让JavaScript
代码的下载不阻塞构建DOM Tree
,则需要用到下面script
元素的两个属性
defer
属性
<!DOCTYPE html>
<html>
<head>
<title>Defer Example</title>
<script src="first.js" defer></script>
<script src="second.js" defer></script>
</head>
<body>
<h1>Hello, world!</h1>
<script>
document.addEventListener('DOMContentLoaded', function() {
console.log('DOMContentLoaded event triggered');
});
</script>
</body>
</html>
defer
属性告诉浏览器不要等待脚本下载,继续解析HTML
,构建DOM Tree
- 如果所有带有
defer
属性的脚本都已经提前下载好了,它会等待DOM Tree
构建完成,然后触发DOMContentLoaded
事件 - 确保了当
DOMContentLoaded
事件触发时,整个DOM Tree
已经构建完成,所有依赖的脚本都已经执行,可以安全地进行DOM
操作 - 多个带
defer
的脚本是可以保持正确的顺序执行的 defer
可以提高页面的性能,并且推荐放到head
元素中defer
仅适用于外部脚本
async
属性
async
属性也不会阻塞解析HTML
async
脚本不能保证顺序,它是独立下载、独立运行,不会等待其他脚本async
不能保证在DOMContentLoaded
之前或者之后执行;async
通常用于独立的脚本,对其他脚本和DOM
没有依赖的