📣读完这篇文章里你能收获到
在JavaScript中,对DOM的操作可能会引发浏览器的回流和重绘过程,这两个过程对页面性能有重要影响。
当DOM元素的几何属性(如尺寸、位置)发生变化,或者网页的CSS样式改变时,浏览器需要重新计算元素的布局,这个过程称为回流。回流不仅影响发生改变的元素本身,还可能影响其后代元素以及同级元素的位置。回流对性能影响较大。
以下是一些常见的DOM操作,它们可能会触发回流:
// 改变元素的几何属性(触发回流)
let element = document.getElementById("myElement");
element.style.width = "200px";
// 添加可见的DOM元素(触发回流)
let newElement = document.createElement("div");
document.body.appendChild(newElement);
// 改变元素的定位方式(触发回流)
let element = document.getElementById("myElement");
element.style.position = "absolute";
// 修改影响元素布局的CSS样式(触发回流)
let element = document.getElementById("myElement");
element.style.display = "flex";
// 激活CSS伪类(触发回流)
let element = document.getElementById("myElement");
element.classList.add("hovered");
// 获取布局信息(可能触发回流)
let element = document.getElementById("myElement");
let computedStyle = window.getComputedStyle(element);
当元素的外观属性(如颜色、背景色、边框样式)发生变化,但不影响其几何属性时,浏览器只需要重新绘制受影响的部分,这个过程称为重绘。
以下是一些常见的DOM操作,它们可能会触发重绘:
// 改变元素的颜色、背景色、边框样式等外观属性(触发重绘)
let element = document.getElementById("myElement");
element.style.backgroundColor = "red";
// 改变元素的透明度(触发重绘)
let element = document.getElementById("myElement");
element.style.opacity = 0.5;
// 修改文本内容(触发重绘)
let element = document.getElementById("myElement");
element.textContent = "New text";
// 激活CSS伪类(触发重绘)
let element = document.getElementById("myElement");
element.classList.add("hovered");
// 在循环中修改DOM(多次触发回流和重绘)
for (let i = 0; i < 100; i++) {
let childElement = document.createElement("div");
childElement.textContent = i;
document.body.appendChild(childElement);
}
以下是一些优化DOM操作以减少回流和重绘的策略,结合示例代码进行说明:
如果需要在循环中修改多个元素,考虑先创建一个脱离文档的节点树(如documentFragment),在循环中修改这个临时节点树,最后将其插入到文档中。
//=> 创建文档碎片
let frg = document.createDocumentFragment();
data.forEach((item)=> {
let List = document.createElement('li');
//=> 每次把元素放入文档碎片中
frg.appendChild(li)
});
//=> 最后再统一放入父元素中
parent.appendChild(frg);
把原有容器中的结构都以字符串的方式获取到,然后拼接成新的字符串,最后统一在插入到原有容器中。
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 元素。但是这种方式很简单,所以真实项目中较常用。
把对 DOM 的读和写分开放到一起。减少在写操作的过程中又进行读操作,是基于前面的浏览器自身优化策略实现的。一次性把样式都操作完成。
// 引发两次回流
box.style.top = '100px';
box.style.top;
box.style.left = '100px';
// 引发一次回流
box.style.top = '100px';
box.style.left = '100px';
box.style.top;
将多个修改操作累积起来,然后一次性应用,可以避免多次触发回流和重绘。
// 避免不必要的DOM操作:累积修改并一次性应用
let elementsToUpdate = document.querySelectorAll(".myClass");
let styleChanges = [];
elementsToUpdate.forEach((element) => {
styleChanges.push({
element: element,
width: "200px",
});
});
styleChanges.forEach(({ element, width }) => {
element.style.width = width;
});
在需要频繁访问的属性或方法上缓存结果,避免重复计算和查询。
let myElement = document.getElementById("myElement");
let myStyle = window.getComputedStyle(myElement);
function updateStyles() {
// 使用缓存的myStyle对象,避免重复查询
let currentWidth = myStyle.getPropertyValue("width");
// ...
}
现代浏览器提供了许多性能工具,可以帮助我们检测和分析回流与重绘: