import{_ as s,o as n,c as a,a as e}from"./app.ab8d0b9f.js";const m=JSON.parse('{"title":"深入理解浏览器运行原理","description":"","frontmatter":{},"headers":[{"level":2,"title":"网页解析过程","slug":"网页解析过程","link":"#网页解析过程","children":[]},{"level":2,"title":"HTML解析过程","slug":"html解析过程","link":"#html解析过程","children":[]},{"level":2,"title":"生成CSS规则","slug":"生成css规则","link":"#生成css规则","children":[]},{"level":2,"title":"构建Render Tree","slug":"构建render-tree","link":"#构建render-tree","children":[]},{"level":2,"title":"布局和绘制(Layout & Paint)","slug":"布局和绘制-layout-paint","link":"#布局和绘制-layout-paint","children":[]},{"level":2,"title":"回流和重绘(Reflow & )","slug":"回流和重绘-reflow","link":"#回流和重绘-reflow","children":[]},{"level":2,"title":"特殊解析: composite合成","slug":"特殊解析-composite合成","link":"#特殊解析-composite合成","children":[{"level":3,"title":"案例1:同一层渲染","slug":"案例1-同一层渲染","link":"#案例1-同一层渲染","children":[]},{"level":3,"title":"案例2:分层渲染","slug":"案例2-分层渲染","link":"#案例2-分层渲染","children":[]},{"level":3,"title":"案例3:transform 3D","slug":"案例3-transform-3d","link":"#案例3-transform-3d","children":[]},{"level":3,"title":"案例4:transition+transform","slug":"案例4-transition-transform","link":"#案例4-transition-transform","children":[]},{"level":3,"title":"案例5:transition+opacity","slug":"案例5-transition-opacity","link":"#案例5-transition-opacity","children":[]},{"level":3,"title":"总结","slug":"总结","link":"#总结","children":[]}]},{"level":2,"title":"浏览器对script元素的处理","slug":"浏览器对script元素的处理","link":"#浏览器对script元素的处理","children":[]},{"level":2,"title":"defer属性","slug":"defer属性","link":"#defer属性","children":[]},{"level":2,"title":"async属性","slug":"async属性","link":"#async属性","children":[]}],"relativePath":"article/深入理解浏览器运行原理.md","lastUpdated":1681575540000}'),l={name:"article/深入理解浏览器运行原理.md"},p=e(`
输入域名 => DNS解析为IP => 目标服务器返回index.html
DNS:Domain Name System
index.html
文件,当遇到<link>
则向服务器请求下载.css
文件<script>
标签则向服务器请求下载.js
文件在解析的过程中,如果遇到<link>
元素,那么会由浏览器负责下载对应的CSS文件
浏览器下载完CSS文件后,就会对CSS文件进行解析,解析出对应的规则树:
有了DOM Tree和CSSOM Tree之后,就可以将二者结合,构建Render Tree了
此时,如果有某些元素的CSS属性display: none;
那么这个元素就不会出现在Render Tree中
第四步是在渲染树(Render Tree)上运行布局(Layout),以计算每个节点的几何体
第五步是将每个节点绘制(Paint)到屏幕上
回流也可称为重排
理解回流(Reflow):
什么情况下会引起回流?
理解重绘(Repaint):
什么情况下会引起重绘?
回流一定会引起重绘,所以回流是一件很消耗性能的事情
开发中要尽量避免发生回流
修改样式尽量一次性修改完毕
尽量避免频繁的操作DOM
尽量避免通过getComputedStyle获取元素尺寸、位置等信息
对某些元素使用position的absolute或fixed属性
在绘制的过程中,可以将布局后的元素绘制到多个合成图层中
标准流 => LayouTree => RenderLayer
\`position:fixed;\` => RenderLayer
默认情况,标准流中的内容都是被绘制在同一个图层(Layer)中的
而一些特殊的属性,浏览器会创建一个新的合成层(CompositingLayer),并且新的图层可以利用GPU来加速绘制
当元素具有哪些属性时,浏览器会为其创建新的合成层呢?
.box1 {
width: 100px;
height: 100px;
background-color: red;
}
.box2 {
width: 100px;
height: 100px;
background-color: blue;
}
<body>
<div class="box1"></div>
<div class="box2"></div>
</body>
在开发者工具的图层工具中可以看到,两个元素.box1
和 .box2
都是在一个层(Document)下渲染的:
当我们为.box2
添加上position: fixed;
属性,这时.box2
将在由浏览器创建出来的合成层,分层单独渲染
.box2 {
width: 100px;
height: 100px;
background-color: blue;
position: fixed;
}
为元素添加上transform
属性时,浏览器也会为对应元素创建一个合成层,需要注意的是:只有3D的变化浏览器才会创建
如果是translateX
或translateY
则不会
.box2 {
width: 100px;
height: 100px;
background-color: blue;
/* position: fixed; */
transform: translateZ(10px);
}
当我们为元素添加上动画时,动画的中间执行过程的渲染会在新的图层上进行,但是中间动画渲染完成后,结果会回到原始图层上
.box2 {
width: 100px;
height: 100px;
background-color: blue;
transition: transform 0.5s ease;
}
.box2:hover {
transform: translateY(10px);
}
transform
执行动画性能更高的原因,因为浏览器会为动画的执行过程单独创建一个合成层top
left
等定位属性实现的动画,是在原始的图层上渲染完成的。“牵一发则动全身”,动画过程中将导致整个渲染树回流与重绘,极大的影响性能与transform
类似,使用transition
过渡的opacity
动画,浏览器也会为其创建一个合成层
.box2 {
width: 100px;
height: 100px;
background-color: blue;
opacity: 1;
transition: opacity 0.5s ease;
}
.box2:hover {
opacity: 0.2;
}
分层确实可以提高性能,但是它是以内存管理为代价的,因此不应当作为Web性能优化策略的一部分过度使用
之前我们说到,在解析到link
标签时,浏览器会异步下载其中的css文件,并在DOM树构建完成后,将其与CSS Tree合成为RenderTree
但是当浏览器解析到script
标签时,整个解析过程将被阻塞,当前script
标签后面的DOM树将停止解析,直到当前script
代码被下载、解析、执行完毕,才会继续解析HTML,构建DOM树
为什么要这样做呢?
script
标签时,会优先下载和执行Javascript代码,而后再继续构建DOM树这也会带来新的问题,比如在现代的页面开发中:
为了解决这个问题,浏览器的script
标签为我们提供了两个属性(attribute):defer
和 async
defer
即推迟,为script
标签添加这个属性,相当于告诉浏览器:不要等待此脚本下载,而是继续解析HTML,构建DOM Tree
DOMContentLoaded
事件触发之前先执行defer
中的代码<script>
console.log('script enter')
window.addEventListener('DOMContentLoaded', () => {
console.log('DOMContentLoaded enter')
})
</script>
<script src="./defer.js" defer></script>
// defer.js
console.log('defer script enter')
上述代码在控制台的输出为:
script enter
defer script enter
DOMContentLoaded enter
defer
的脚本也是按照自上至下的顺序执行的defer
可以提高页面的性能,并且推荐放到head
元素中defer
仅适用于外部脚本,对于script
标签内编写的默认JS
代码会被忽略掉async
属性也可以做到:让脚本异步加载而不阻塞DOM树的构建,它与defer
的区别:
async
标记的脚本是完全独立的async
脚本不能保证执行顺序,因为它是独立下载、独立运行,不会等待其他脚本async
标记的脚本不会保证它将在DOMContentLoaded
之前或之后被执行要使用async
属性标记的script
操作DOM,必须在其中使用DOMContentLoaded
监听器的回调函数,在该事件触发(DOM树构建完毕)后,执行相应的回调函数