对于这个问题,我将引用
querySelector
和
querySelectorAll
作为querySelector*,并将
getElementById
、
getElementsByClassName
、
getElementsByTagName
和
getElementsByName
作为getElement*。
很多这些信息可以在规范中进行验证,其中很多是我在编写时运行的各种基准测试得出的结果。规范链接:
https://dom.spec.whatwg.org/
主要区别
querySelector*更灵活,因为你可以传递任何CSS3选择器给它,而不仅仅是简单的id、标签或类选择器。
querySelector*的性能随着它所调用的DOM的大小而变化。准确地说,querySelector*的调用时间复杂度为O(n),而getElement*的调用时间复杂度为O(1),其中n是元素或文档的所有子元素的总数。
这些调用的返回类型各不相同。querySelector和getElementById都返回单个元素。querySelectorAll和getElementsByName都返回NodeList。较旧的getElementsByClassName和getElementsByTagName都返回HTMLCollection。NodeList和HTMLCollection都被称为元素的集合。
集合可以分别返回“动态”或“静态”集合。这并不反映它们实际返回的类型。getElements*调用返回动态集合,而querySelectorAll返回静态集合。据我理解,动态集合包含对DOM中元素的引用,而静态集合包含元素的副本。请参考@Jan Feldmann的评论,他提供了另一种角度。我还没有找到一个好的方法将其融入到我的答案中,但这可能是一个更准确的理解。
这些概念在下表中进行了总结。
Function | Live? | Type | Time Complexity
querySelector | | Element | O(n)
querySelectorAll | N | NodeList | O(n)
getElementById | | Element | O(1)
getElementsByClassName | Y | HTMLCollection | O(1)
getElementsByTagName | Y | HTMLCollection | O(1)
getElementsByName | Y | NodeList | O(1)
细节、技巧和示例
HTMLCollections不像NodeLists那样类似于数组,不支持.forEach()方法。我发现使用扩展运算符可以解决这个问题:
[...document.getElementsByClassName("someClass")].forEach()
每个元素和全局document都可以访问所有这些函数,除了getElementById和getElementsByName,它们只在document上实现。
与使用querySelector*相比,使用getElement*调用来链接元素将提高性能,特别是在非常大的DOM上。即使在小的DOM和/或非常长的链上,它通常也更快。然而,除非你知道你需要性能,否则应该优先选择querySelector*的可读性。querySelectorAll通常更难重写,因为你必须在每一步中从NodeList或HTMLCollection中选择元素。例如,以下代码
不起作用:
document.getElementsByClassName("someClass").getElementsByTagName("div")
因为你只能在单个元素上使用getElements*,而不是集合,但如果你只想要一个元素,那么:
document.querySelector("#someId .someClass div")
可以写成:
document.getElementById("someId").getElementsByClassName("someClass")[0].getElementsByTagName("div")[0]
注意在每一步中使用[0]来获取返回集合的第一个元素,最终得到一个元素,就像querySelector一样。
由于所有元素都可以使用querySelector*和getElement*调用,你可以使用这两个调用来构建链,如果你想要一些性能提升,但不能避免一个无法用getElement*调用写的querySelector,这可能会很有用。
虽然通常很容易判断一个选择器是否可以只使用getElement*调用来编写,但有一种情况可能不明显:
document.querySelectorAll(".class1.class2")
可以重写为
document.getElementsByClassName("class1 class2")
在使用querySelector*获取的静态元素上使用getElement*将导致一个与querySelector复制的DOM的静态子集相关的元素,但与完整的文档DOM不相关...这就是简单的动态/静态元素解释开始分崩离析的地方。你应该尽量避免需要担心这个问题的情况,但如果确实需要,记住querySelector*调用在返回引用之前会复制它们找到的元素,而getElement*调用则直接获取引用而不复制。
querySelector*和getElementById按照前序遍历、深度优先的方式遍历元素,在规范中称为"树顺序"。对于其他getElement*调用,从规范中我无法确定它们是否与树顺序相同,但是getElementsByClassName(".someClass")[0]可能在每个浏览器中的结果不可靠。getElementById("#someId")应该是可靠的,即使在页面上有多个相同的id副本。
当我在一个无限滚动页面上工作时,我不得不研究这个问题,我认为这可能是一个常见的情况,性能成为一个问题。我们的代码中有带有querySelectorAll调用的onScroll事件。即使调用被限制速率,如果你滚动到足够远的位置,页面也会崩溃,此时将有太多的调用迭代通过太多的元素,浏览器无法跟上。DOM的大小在这种情况下是相关的,因此在运行在无限滚动页面上的代码中,更倾向于使用getElement*调用。
document.querySelectorAll(".myclass")
?使用document.querySelector(".myclass")
只会返回第一个匹配的元素。 - mhatch