一个网页从输入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 ModelCSS对象模型)
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没有依赖的