执行 js 有 js 引擎,那么渲染的时候,也有渲染引擎 并且渲染引擎在不同的浏览器中也不是都相同的
浏览器接收到 HTML 文件并转换为 DOM 树
- 打开一个页面 -- > 请求相应的 HTML/XHTML/SVG 文件 -- > js,css,html,均为字符串,但是机器是不认识这些字符串的,在网络中传输的内容其实都是 0 和 1 这些字节数据 -- > 浏览器将字节数据转化为字符串(也就是代码)
- 以下过程叫做标记化
字符串 -- > 浏览器将字符串通过语法分析转换为标记
- 什么是标记
标记还是字符串,是构成代码的最小单位,这一过程会把代码分拆成一块块,并给这些内容打上标记,便于理解这些最小单位的代码是什么意思
当结束标记后,这些标记会紧挨着转换为 Node,最后这些 Node 会根据不同 Node 之前的联系构建成一颗 DOM 树
将 CSS 文件转换为 CSSDOM 树
过程
资源消耗
上面过程是消耗资源的
解析:
<div>
<a> <span></span> </a>
</div>
<style>
span {
color: red;
}
div > a > span {
color: red;
}
</style>
对于第一种方式:浏览器只需要找到页面所有的 span 标签然后设置颜色;
对于第二种方式:浏览器找到所有的 span 标签,然后找到 span 标签上的 a 标签,最后去找到 div 标签,然后给符合这种条件的 span 设置颜色,这样递归过程就很复杂。
所以我们要尽量避免写过于具体的 CSS 选择器,然后对于 HTML 来说也尽量少的添加无意义标签,保证层级扁平。
生成渲染树
当我们生成 DOM 树和 CSSDOM 树之后,需要将这两棵树组合成渲染树
在这一过程中,不是简单的将两者合并就行了,渲染树只会包含需要显示的节点和这些节点的样式信息,如果某个节点是 display:none 的,那么就不会在渲染树中显示了。
当浏览器生成渲染树以后,就会根据渲染树来进行布局(也叫做回流),然后调用 GPU 绘制,合成图层,显示在屏幕上。
为什么操作 DOM 慢
因为 DOM 是属于渲染引擎中的东西,而 JS 是 JS 引擎中的东西。
当我们通过 JS 操作 DOM 的时候,其实这个操作涉及到了两个线程之间的通信,势必会带来性能伤的损耗。
操作 DOM 次数一多,也就等同于一直在进行线程之间的通信,而且操作 DOM 可能还会带来重绘回流的情况,所以也就导致了性能上的问题
经典面试题:插入几万个 DOM,如何实现页面不卡顿?
- requestAnimationFram 的方式去循环的插入 DOM
- 虚拟滚动
这种技术的原理就是只渲染可视区域内的内容,非可见区域的那就完全不渲染了,当用户在滚动的时候就实时去替换渲染的内容
什么情况阻塞渲染
- 首先渲染的前提是生成渲染树,所以 HTML,CSS 肯定会阻塞渲染。如果你想渲染的越快,越应该降低一开始需要渲染的文件大小,而且扁平层级,优化选择器
- 当浏览器解析到 script 标签的时候,会暂停构建 DOM,完成后才会从暂停的地方重新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 js 文件,这也是都建议将 script 标签放在 body 标签底部的原因
- 当然在当下,并不是说 script 标签必须放在底部,你可以给 script 标签添加 defer/async 属性
- 当 script 标签加上 defer 属性以后,表示该 js 文件会并行下载,但是会放到 HTML 解析完成后顺序执行,所以这种情况,你可以把 script 标签放在任何位置
- 对于没有任何依赖的 js 文件可以加上 async 属性,表示 JS 文件下载和解析不会阻塞渲染
补充:async 和 defer 有什么区别
- 情况一:
<script src='./script.js'></script>
没有 async,defer ,浏览器遇到会立即加载并执行脚本,并不会等待后续加载的文档,读到就加载并执行
- 情况二:异步下载
<script async src='./script.js'></script>
async 的意思是异步执行引入的 js
与 defer 的区别是,加载完了便会直接开始执行--无论此刻是 HTML 解析阶段还是 DOMContentLoaded 触发之后。需要注意的是,这种方式加载的 JavaScript 依然会阻塞 load 事件
scync-script 可能在 DOMcontectLoade 之前执行,也可能在 DOMcontectLoade 之后执行,但是肯定在 load 执行之前执行,即会阻塞 load 事件的执行
DOMcontectLoad 时间在 HTML 加载完成后执行,load 在整个页面以及所有依赖资源都加载完执行
- 情况三:延迟执行
<script defer src='./script.js'></script>
defer 表示延迟执行引入的 script,表示该 js 文件会并行下载,但是会放到 HTML 解析完成后顺序执行,所以这种情况,你可以把 script 标签放在任何位置
重绘和回流
-
重绘 当前节点需要更改外观而不影响布局的,比如改变 color,就叫重绘
-
回流 布局或者几何属性发生该拜年就称为回流
-
回流必定引起重绘,重绘比一定引起回流
以下几个动作可能导致性能问题
- 改变 window 大小
- 改变字体
- 添加或删除样式
- 文字改变
- 定位或者浮动
- 盒子模型
减少重绘和回流
- 用 transform 替换 top(减少元素的回流)
- 使用 visibility 替代 dispalay: none 前者引起重绘,后者引起回流
- 不要属性值放在循环内取值
for(let i = 0; i < 1000; i++) {
// 获取 offsetTop 会导致回流,因为需要去获取正确的值
console.log(document.querySelector('.test').style.offsetTop)
}```
- 不要使用 table 布局,一个很小的改动可能导致整个 table 重新布局
- 动画实现速度越快,发生回流的次数越多,所以动画可以使用 requestAnimationFram
- css 选择器从右往左找,要避免层级过多
- 将频繁回流和冲绘的元素设为一个图层,图层能够防止该节点的行为影响其他节点,比如 video 标签,默认设置为一个图层