e.matches()方法是否必须遍历DOM来评估选择器?
我问是因为以下情况:
if ( e.matches('.class_1, .class_1 *') {
/* Do something. */
} else if ( e.matches('.class_2, .class_2 *') {
/* Do something. */
} else if ( e.matches('.class_3, .class_3 *') {
/* Do something. */
} else {
/* Do something. */
}
如果从元素
e
到祖先树向上遍历DOM,判断e.matches()是否为true/false;然后,当先前的条件为假时,它将对每个 else if
重复该过程。所以,我想知道这是否效率不高,我应该使用 e.classList.contains()
循环一次祖先树来查找首先遇到的集合(class_1、class_2、class_3)中的哪一个,或者到达指示应该停止搜索的祖先元素。但contains()
似乎也不是一个快速测试。
let c = e.classList;
while ( e ) {
if ( c.contains('class_1') ) {
/* Do something. */
break;
} else if ( c.contains('class_2') ) {
/* Do something. */
break;
} else if ( c.contains('class_3') ) {
/* Do something. */
break;
}
e = e.parentNode;
}
在此 DOM 结构中,class_1 始终比 class_2 更深,而 class_2 始终比 class_3 更深。所以,我想首先确定
e
属于哪个级别。不需要确定哪个元素是 class_1、2、3,而只需确定它们中的哪一个(如果有)e
在树的上行路径上最接近。
谢谢你。
于 2023 年 12 月 24 日添加
考虑这个问题并尝试这个小例子已经过去了几个月。结果有点出乎意料,因为
e.closest()
和 e.matches()
几乎相同,并且当有匹配元素时速度非常快;但是,当没有匹配时,e.closest()
会慢得多,而 e.matches()
则相同。在 Firefox 中运行这个。在 Brave 中尝试过,5000 次迭代导致它在 SO 中崩溃;但它运行的迭代次数较少,并且结果相似。
看来文档(https://dom.spec.whatwg.org/#ref-for-dom-element-closest%E2%91%A0)虽然我不明白它的确切含义,表明两种方法之间的过程存在差异。当有比赛时,从内向外计算,2500级的
evt_3
和5000级的evt_1
没有时间差。
var
D = document.createElement("DIV"),
d = D;
D.className = "evt_1";
for ( let i = 0; i < 5000; i++ ) {
d = d.appendChild(document.createElement("DIV"));
if ( i === 2500 ) d.className = "evt_3";
}
document.body.append(D);
let t = Date.now();
console.log( d.matches(".evt_1, .evt_1 *") + " " + (Date.now() - t) );
t = Date.now();
console.log( d.closest(".evt_1, .evt_1 *") + " " + (Date.now() - t) );
t = Date.now();
console.log( d.matches(".evt_2, .evt_2 *") + " " + (Date.now() - t) );
t = Date.now();
console.log( d.closest(".evt_2, .evt_2 *") + " " + (Date.now() - t) );
t = Date.now();
console.log( d.matches(".evt_3, .evt_3 *") + " " + (Date.now() - t) );
t = Date.now();
console.log( d.closest(".evt_3, .evt_3 *") + " " + (Date.now() - t) );
/*
true 0
[object HTMLDivElement] 0
false 0
null 112
true 0
[object HTMLDivElement] 0
*/
它可能必须遍历祖先列表,因此可能会有些浪费,但自己遍历一次可能不会比浏览器遍历3次更快。
如果网站存在明显的性能问题(例如,如果多次执行此操作),您可以在不同的浏览器上测量这两种方法,看看哪种方法更快。但在大多数情况下,实际上没有什么区别,那么最好选择让代码更清晰的东西。
closest()
使用类名的选择器列表:
document.querySelectorAll(".clickable").forEach((div) => {
div.addEventListener("click", (ev) => {
console.log(ev.currentTarget.closest(".class_1, .class_2, .class_3"));
});
});
<div>
<div class="class_3">
<div class="class_2">
<div class="class_1">
<div class="clickable">Click 1</div>
</div>
</div>
</div>
<div class="class_3">
<div class="class_2">
<div class="clickable">Click 2</div>
</div>
</div>
<div class="class_3">
<div class="clickable">Click 3</div>
</div>
</div>
您可以通过分析已分配给单击(或以其他方式选择)元素的类来在任何循环之外完成“繁重”工作:
document.querySelectorAll("div").forEach(d=>d.onclick=e=>{
const classes=Object.fromEntries([...e.target.classList].map(cl=>([cl,1])));
// classes is an object that has class-named properties. These can be easily checked for existence:
if (classes.class_1) console.log("A class 1 element was clicked");
else if (classes.class_2) console.log("A class 2 element was clciked");
else if (classes.class_3) console.log("A class 3 element was clciked");
})
.class_1,.class_2,.class_3 {cursor:pointer; background-color:#ddd; margin:6px}
<p>Please click on any of the divs below:</p>
<div class="class_1">class 1 item</div>
<div class="class_2">class 2 item</div>
<div class="class_3">class 3 item</div>
<div class="class_1 class_2">class 1 and 2 item</div>
<div class="class_2 class_3">class 2 and 3 item</div>
<div class="class_3 class_1">class 3 and 1 item</div>
看到您的
if
语句序列总是过滤掉按字母顺序排列最低的“class_...”条目,您可以进一步简化脚本:
document.querySelectorAll("div[class*=class_]").forEach(d=>d.onclick=e=>{
const minClass=[...e.target.classList].filter(c=>c.slice(0,6)=="class_").reduce((a,c)=>c<a?c:a)
if (minClass=="class_1") console.log("A class 1 element was clicked");
else if (minClass=="class_2") console.log("A class 2 element was clciked");
else if (minClass=="class_3") console.log("A class 3 element was clciked");
})
.class_1,.class_2,.class_3 {cursor:pointer; background-color:#ddd; margin:6px}
<p>Please click on any of the divs below:</p>
<div class="class_1">class 1 item</div>
<div class="class_2">class 2 item</div>
<div class="class_3">class 3 item</div>
<div class="extraClass">this chould not be clickable</div>
<div class="class_1 abc class_2">class 1 and 2 item</div>
<div class="class_2 class_3">class 2 and 3 item</div>
<div class="class_3 class_1">class 3 and 1 item</div>