DOM(Document Object Model)树代表了文档的层次结构,它允许开发者通过编程方式访问和修改HTML或XML文档的内容。当我们需要对DOM树中的元素进行批量操作或数据分析时,遍历DOM树就显得尤为重要。
下面提供的 DOMIndexer
类代码完成了对可见元素进行索引。如下所示:
js
class DOMIndexer {
constructor() {
if (DOMIndexer.instance) {
return DOMIndexer.instance;
}
DOMIndexer.instance = this;
}
isElementVisible(element) {
return window.getComputedStyle(element).display !== 'none';
}
traverseAndIndexVisibleElements(parentNode, cb) {
if(this.isElementVisible(parentNode)){
cb(parentNode);
const children = parentNode.children;
if(children?.length){
for (let i = 0; i < children.length; i++) {
const child = children[i];
this.traverseAndIndexVisibleElements(child, cb);
}
}
}
}
detachAndReattach(rootNode, callback, detach=true) {
if(!detach){
callback();
return;
}
// 创建一个占位元素,用于在根元素脱离文档时保持其位置
const placeholder = document.createElement('div');
// 插入占位符到根节点的位置
const parent = rootNode.parentNode;
parent.insertBefore(placeholder, rootNode);
// 脱离根元素
parent.removeChild(rootNode);
// 设置一个标志,表示根节点已被脱离
rootNode.detached = true;
// 执行回调,这里是遍历和索引DOM的操作
callback();
// 删除标志
delete rootNode.detached;
// 重新将根节点插入到文档中,替换占位元素
parent.replaceChild(rootNode, placeholder);
}
indexDOM(cb, root = document.body) {
this.detachAndReattach(root, () => {
// 遍历并索引可见元素
this.traverseAndIndexVisibleElements(root, cb);
}, false);
}
}
// 实现单例模式
let instance = null;
let index = 0;
const exec = (element) => {
element.setAttribute('data-index', index++);
}
DOMIndexer.getInstance = function () {
if (!instance) {
instance = new DOMIndexer();
}
return instance;
};
// 使用示例
const indexer = DOMIndexer.getInstance();
indexer.indexDOM(exec);
下面介绍这段代码的实现原理:
1. DOMIndexer类简介
DOMIndexer
类是一个实现了单例模式的工具类,用于遍历和索引DOM树中的可见元素。它提供了几个关键方法,包括判断元素是否可见、遍历并索引可见元素,以及在遍历过程中临时脱离和重新附加根节点,以减少重排和重绘的性能开销。
2. isElementVisible方法
这个方法用于判断一个DOM元素是否可见。它利用window.getComputedStyle
方法来获取元素的计算样式,并检查display
属性是否不等于none
。如果display
不是none
,则说明元素是可见的。
3. traverseAndIndexVisibleElements方法
这是DOMIndexer
类的核心方法之一。它是一个递归函数,从给定的父节点开始,遍历其所有子节点。对于每个可见的子节点,它都会调用传入的回调函数cb
,并将该节点作为参数传递。然后,它会递归地对每个子节点执行相同的操作。
4. detachAndReattach方法
在遍历DOM树之前,detachAndReattach
方法会先将根节点从文档中临时脱离,以减少浏览器在遍历过程中的重排和重绘次数。脱离后,它会执行传入的回调函数(通常是遍历和索引操作),然后再将根节点重新附加到文档中。
5. indexDOM方法
这是对外暴露的主要方法,用于启动整个遍历和索引过程。它接受一个回调函数作为参数,这个回调函数定义了如何对每个可见元素进行索引。默认情况下,遍历的起点是文档的body
元素。
6. 使用示例
示例中首先通过DOMIndexer.getInstance()
获取DOMIndexer
类的单例实例。然后定义一个名为exec
的回调函数,该函数简单地为每个可见元素设置一个data-index
属性,并使用一个全局变量index
来递增赋值。最后调用indexer.indexDOM(exec)
来启动遍历和索引过程。
通过DOMIndexer
类,可以高效地遍历DOM树中的可见元素,并对它们进行自定义操作(如索引、修改等)。这种遍历方式在处理大型DOM树时尤为有用,因为它可以显著减少浏览器的重排和重绘次数,从而提高性能。同时,由于采用了单例模式,可以确保在整个应用中只有一个DOMIndexer
实例,从而节省内存资源。
应用
使用上面提供的 DOMIndexer
类对指定DOM元素及其所有子元素的位置信息进行统计。
js
let instance = null;
let index = 0;
const elementsInfo = {};
const exec = (element) => {
// 设置 data-index 属性
element.setAttribute('data-index', index++);
// 获取元素的宽高、top 和 left 值
const rect = element.getBoundingClientRect();
const position = getComputedStyle(element).position;
const width = rect.width;
const height = rect.height;
const top = rect.top;
const left = rect.left;
// 以 data-index 的值为键,将统计值存放在结构体中
const dataIndex = element?.getAttribute('data-index');
elementsInfo[dataIndex] = { width, height, top, left, position };
};
DOMIndexer.getInstance = function () {
if (!instance) {
instance = new DOMIndexer();
}
return instance;
};
// 使用示例
const indexer = DOMIndexer.getInstance();
indexer.indexDOM(exec);
console.log('elementsInfo:', elementsInfo)
exec回调函数与元素信息收集
示例中定义了一个名为exec
的回调函数。这个函数对每个可见元素执行以下操作:
- 设置一个
data-index
属性,用于唯一标识该元素。 - 使用
getBoundingClientRect
方法获取元素的布局信息,如宽度、高度、顶部和左边的位置。 - 使用
getComputedStyle
方法获取元素的定位信息。 - 将这些信息存储在一个全局对象
elementsInfo
中,以便后续使用。
应用场景
这种遍历DOM树并收集元素信息的方法在多个场景中都非常有用。例如:
- 性能分析:通过收集页面上所有可见元素的布局信息,可以分析哪些元素占用了过多的空间或导致页面加载缓慢。
- 自动化测试:在自动化测试脚本中,可以使用这种方法来验证页面上的元素是否按预期显示和布局。
- 动态样式调整:根据收集到的元素信息,可以动态地调整页面的样式,以提供更好的用户体验。
总结
通过DOMIndexer
类可以高效地遍历DOM树中的可见元素,并收集它们的布局信息。这种方法在Web开发中具有广泛的应用价值,无论是用于性能分析、自动化测试还是动态样式调整。