DOM 的回流(reflow)重绘(repaint)。
1. 浏览器页面渲染流程
浏览器把获取到的HTML代码解析成1个DOM树。DOM树里包含了所有HTML标签,包括 display:none 隐藏,还有用JS动态添加的元素等。
浏览器把所有样式(用户定义的CSS和用户代理)解析成样式结构体,在解析的过程中会去掉浏览器不能识别的样式
DOM Tree 和样式结构体组合后构建render tree, render tree类似于DOM tree,但区别很大,render tree能识别样式,render tree中每个NODE都有自己的style,而且 render tree不包含隐藏的节点 (比如display:none的节点,还有head节点),因为这些节点不会用于呈现,而且不会影响呈现的,所以就不会包含到 render tree中。注意 visibility:hidden隐藏的元素还是会包含到 render tree中的,因为visibility:hidden 会影响布局(layout),会占有空间。根据CSS2的标准,render tree中的每个节点都称为Box (Box dimensions),理解页面元素为一个具有填充、边距、边框和位置的盒子。
一旦render tree构建完毕后,浏览器就可以根据render tree来绘制页面了。
2. 重绘
当某一个 DOM 元素样式更改(只影响元素的外观、风格等),浏览器会重新渲染这个元素,这就是重绘。
box.style.color = "red";
//...中间还有一些代码
box.style.fontSize = '16px';
//=> 修改紧挨的话,也只是触发一次重绘。
上面操作触发了两次重绘,性能上有所消耗,真实项目中为了优化这个性能,我们最好一次性把需要修改的样式搞定。
//=> 修改放在一起
box.style.color = "red";
box.style.fontSize = '16px';
//... 执行其他代码
//=> 直接使用类来修改样式
.xxx {
color: red;
fontSize: '16px';
}
box.className = 'xxx';
重绘会消耗性能,但是不会消耗太大。相当于从浏览器渲染的第三步开始。
3. 回流
当 DOM 元素的结构或者位置发生改变都会引发回流。
添加或删除可见的 DOM 元素
元素位置的改变
元素尺寸的改变(边距、填充、边框、宽度、高度)
内容改变(文本改变或者图片大小改变而引起的计算值宽度和高度改变)
页面渲染初始化
浏览器窗口尺寸改变(resize 事件发生时)
回流特别消耗性能,需要从第一步开始重新渲染。
4. 优化
4.1 浏览器自身的优化策略
浏览器会维护一个队列,把所有会引起回流、重绘的操作放入这个队列。
等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会释放队列,进行一个批处理。
这样就会让多次的回流、重绘变成一次回流重绘。
但有时候我们写的一些代码可能会强制浏览器提前释放队列,这样浏览器的优化可能就起不到作用了。比如:当你向浏览器请求一些样式信息的时候,就会让浏览器释放队列,才能获取的最新的样式。
4.2 基于文档碎片减少回流
在虚拟空间中开辟的一个容器:
每当创建一个元素,先把它存放到文档碎片中(千万不要放到页面中,避免回流)
当我们把需要的元素都创建完成,并且都添加到文档碎片中,再统一把文档碎片放到页面中(只会引发一次回流)
//=> 创建文档碎片
let frg = document.createDocumentFragment();
data.forEach((item)=> {
let List = document.createElement('li');
//=> 每次把元素放入文档碎片中
frg.appendChild(li)
});
//=> 最后再统一放入父元素中
parent.appendChild(frg);
//=> 手动销毁
frg = null;
4.3 基于字符串拼接减少回流
把原有容器中的结构都以字符串的方式获取到,然后拼接成新的字符串,最后统一在插入到原有容器中。
let str = ``;
data.forEach(function (item) {
str += `<li data-time="${time}" data-hot="${hot}" data-price="${price}">
<a href="#">
<img src="${picImg}" alt="">
<p title="${title}">${title}</p>
<span>¥${price}</span>
<span>${hot}</span>
<span>${time}</span>
</a>
</li>`;
});
parent.innerHTML = str;
理论上说,这种方式性能不如前面提到的文档碎片。因为这里还要把字符串变成 HTML 元素。但是这种方式很简单,所以真实项目中较常用。
4.4 分离读写
把对 DOM 的读和写分开放到一起。减少在写操作的过程中又进行读操作,是基于前面的浏览器自身优化策略实现的。一次性把样式都操作完成。
// 引发两次回流
box.style.top = '100px';
box.style.top;
box.style.left = '100px';
// 引发一次回流
box.style.top = '100px';
box.style.left = '100px';
box.style.top;