这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战;
写在前面
在了解如何操作DOM之前,我们先来了解下DOM的概念。DOM(Document Object Modal,文档对象模型),是JavaScript操作HTML的接口。
DOM主要由3个部分组成:
- DOM节点
- DOM事件
- 选择区域 (选择区域的使用场景有限,一般用于富文本编辑类业务)
注意区分:
标签是HTML的基本单位,比如;p、div、input;节点是DOM的基本单位,有多种类型,比如注释节点、文本节点;元素是节点的一种,与HTML标签相对应,比如p标签会对应p元素。
为什么DOM操作耗时
浏览器的工作机制
浏览器包含渲染引擎和JavaScript引擎,他们属于单线程运行。单线程优势是开发方便,避免多线程下的死锁、竞争等问题,劣势是失去了并发能力。
每次 DOM 操作就会引发线程的上下文切换——从 JavaScript 引擎切换到渲染引擎执行对应操作,然后再切换回 JavaScript 引擎继续执行,这就带来了性能损耗。如果频繁地大量切换,那么性能问题也就逐渐暴露了。
如果在循环中包含一些复杂的逻辑或者说涉及到多个元素时,就会造成不可忽视的性能损耗。
重新渲染 (重排和重绘)
浏览器在渲染页面时,如果在操作DOM时涉及到元素、样式的修改,就会引起渲染引擎重新计算样式生成css树,同时还有可能触发对元素的重新排布(简称“重排”)和重新绘制(简称“重绘”)。
-
影响到其他元素排布的操作就会引起重排,继而引发重绘。比如:
- 修改元素边距、大小
- 添加、删除元素
- 改变窗口大小
-
引起重绘。比如:
- 设置背景图片
- 修改字体颜色
- 改变 visibility属性值
重排渲染耗时明显高于重绘,重排会导致重绘。
如何高效操作DOM
在循环外操作元素
循环10000次DOM元素和循环10000次JSON对象,所耗时间相差一个量级。
const times = 10000;
// 循环DOM
console.time('dom');
for(let i = 0; i < times; i++) {
document.body === '' ? void 0 : void 0;
}
console.timeEnd('dom'); // dom: 0.955322265625 ms
// 循环JSON
var body = JSON.stringify(document.body);
console.time('json');
for(let i = 0; i < times; i++) {
body === '' ? void 0 : void 0;
}
console.timeEnd('json'); // json: 0.56884765625 ms
即使在循环外也要尽量减少操作元素,因为不知道他人调用你的代码时是否处于循环中。
批量操作元素
示例:要在页面上插入1 万个 div 元素,在循环中直接创建再添加到父元素上耗时会非常多
// 方法1
const times = 10000;
console.time('createElement');
for(let i = 0; i<times; i++) {
const div = document.createElement('div');
document.body.appendChild(div);
}
console.timeEnd('createElement'); // createElement: 39.69921875 ms
// 方法2
const times = 10000;
console.time('innerHTML');
let html = '';
for (let i=0;i<times;i++) {
html += '<div></div>'
}
document.body.innerHTML += html;
console.timeEnd('innerHTML'); // innerHTML: 0.683837890625 ms
虽然通过修改 innerHTML 来实现批量操作的方式效率很高,但它并不是万能的。比如要在此基础上实现事件监听就会略微麻烦,只能通过事件代理或者重新选取元素再进行单独绑定。
缓存元素集合
将通过选择器函数获取到的 DOM 元素赋值给变量,之后通过变量操作而不是再次使用选择器函数来获取。
// 不推荐写法
for (let i = 0; i < document.querySelectorAll('div').length; i++) {
document.querySelectorAll(`div`)[i].innerText = i
}
// 推荐写法(将元素集合赋值给 JavaScript 变量,每次通过变量去修改元素,那么性能将会得到不小的提升。)
const divs = document.querySelectorAll('div')
for (let i = 0; i < divs.length; i++) {
divs[i].innerText = i
}
总结
本篇分析过程:深入理解DOM的必要性=>分析DOM操作耗时的原因=>提出解决办法
关于提升渲染性能,拓展的相关方法:
-
尽量不要使用复杂的匹配规则和复杂的样式,从而减少渲染引擎计算样式规则生成CSSDOM树的时间。
-
尽量减少重排、重绘影响的区域。
-
使用css3特性来实现动画效果。