JavaScript中querySelector和querySelectorAll与getElementsByClassName和getElementById的区别

263
我想知道 querySelectorquerySelectorAllgetElementsByClassName 以及 getElementById 之间的区别。我在这个链接中了解到,使用querySelector可以写成document.querySelector(".myclass")来获取类名为myclass的元素,或者写成document.querySelector("#myid")来获取ID为myid的元素。但是我已经可以用getElementsByClassNamegetElementById实现相同的效果。应该优先选择哪一个呢?
此外,我在XPages工作,其中ID是动态生成的,带有冒号,看起来像这样:view:_id1:inputText1。因此,当我编写document.querySelector("#view:_id1:inputText1")时,它无法正常工作。但是写成document.getElementById("view:_id1:inputText1")就可以工作。有什么想法吗?

1
querySelector用于查询HTML DOM树,其中可以包括HTML元素及其属性作为查询的关键元素...您可以使用正则表达式来实现这一点...dojo.query()也可以做同样的事情。 - anix
1
你是不是想说 document.querySelectorAll(".myclass")?使用 document.querySelector(".myclass") 只会返回第一个匹配的元素。 - mhatch
11个回答

246
对于这个问题,我将引用querySelectorquerySelectorAll作为querySelector*,并将getElementByIdgetElementsByClassNamegetElementsByTagNamegetElementsByName作为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*调用。

17
这是有关该主题的迄今为止最准确的答案。应该得到更多的赞同票。 - SeaWarrior404
2
你的博客应该非常精确,Sasha。 - theking2
1
非常好的文章。关于“live”和“static”集合的部分有些误导人,因为“live”和“static”集合都包含对DOM元素的“引用”。假设我们有一个NodeList和一个HTMLCollection,两者都包含所有类X的元素。如果我将类Y添加到属于NodeList和HTMLCollection的类X元素中,则在两个集合中均会更新该元素。 - Jan Feldmann
1
“实时”集合的特殊之处在于,它们会更新集合本身及其包含的内容。因此,如果我有一个类X元素的实时集合和静态集合,然后将一个类X元素添加到DOM中,那么实时集合将包含该新元素,而静态集合则不会。” - Jan Feldmann
2
我可能会将“主要区别4”更改为类似于“动态集合和静态集合都包含对DOM元素的引用。但是,动态集合会保持DOM元素引用列表的更新,而静态集合则不会”。 - Jan Feldmann
显示剩余11条评论

141
我想知道querySelector和querySelectorAll与getElementsByClassName和getElementById之间的区别是什么?
语法和浏览器支持。
当您想要使用更复杂的选择器时,querySelector更有用。例如,从foo类的成员元素下降的所有列表项:.foo li
document.querySelector(“#view:_id1:inputText1”)不起作用。但编写document.getElementById(“view:_id1:inputText1”)有效。有任何想法为什么?
“:”字符在选择器内具有特殊含义。您必须对其进行转义。(选择器转义字符在JS字符串中也具有特殊含义,因此您还必须转义该字符)。
document.querySelector("#view\\:_id1\\:inputText1")

3
不同的浏览器(和版本)可能会有所不同。我认为基于选择器的方法更昂贵一些(但这种差异可能不会明显到足以引起关注)。 - Quentin
1
我支持@janaspage的说法。今天网站也宕机了。 - doplumi
6
关于选课,还可以看 https://jsperf.com/getelementsbyclassname-vs-queryselectorall/25。结论:应该更倾向于使用纯JavaScript而不是jQuery,并使用特定函数 getElementByIdgetElementsByClassName。没有 getElementsByClassName 的情况下,使用 className 选择器可能会慢 数百倍 - Mouloud85

124

来自Mozilla文档的收集

NodeSelector接口 此规范向实现Document、DocumentFragment或Element接口的任何对象添加了两种新方法:

querySelector

返回节点子树中第一个匹配的Element节点。如果找不到匹配的节点,则返回null。

querySelectorAll

返回包含节点子树中所有匹配的Element节点的NodeList,如果没有找到匹配项,则返回空的NodeList。

注意:通过querySelectorAll()返回的NodeList不是实时的,这意味着DOM中的更改不会反映在集合中。这与返回实时节点列表的其他DOM查询方法不同。


42
感谢你指出了活动节点列表的区别,这是非常重要的差异,取决于你打算如何使用结果。+1 - jmbpiano
7
“live”指的是在DOM运行时添加的节点,并且可以在新添加的节点上工作。 - xkeshav

103
关于两者的区别,最重要的一点在于它们的返回结果不同。在使用querySelectorAll方法时会返回静态集合,而使用getElementsByClassName方法时会返回动态集合。如果你将结果存储在一个变量中以供日后使用,这可能会导致混淆:
  • 使用querySelectorAll生成的变量将包含在调用该方法时满足选择器的元素。
  • 使用getElementsByClassName生成的变量将包含在使用该变量时满足选择器的元素(可能与调用该方法时的元素不同)。
例如,即使您没有重新分配变量aux1aux2,在更新类之后,它们包含的值也是不同的。

// storing all the elements with class "blue" using the two methods
var aux1 = document.querySelectorAll(".blue");
var aux2 = document.getElementsByClassName("blue");

// write the number of elements in each array (values match)
console.log("Number of elements with querySelectorAll = " + aux1.length);
console.log("Number of elements with getElementsByClassName = " + aux2.length);

// change one element's class to "blue"
document.getElementById("div1").className = "blue";

// write the number of elements in each array (values differ)
console.log("Number of elements with querySelectorAll = " + aux1.length);
console.log("Number of elements with getElementsByClassName = " + aux2.length);
.red { color:red; }
.green { color:green; }
.blue { color:blue; }
<div id="div0" class="blue">Blue</div>
<div id="div1" class="red">Red</div>
<div id="div2" class="green">Green</div>


2
只是提一下 - 所有旧的DOM API返回节点列表,即document.getElementsByNamedocument.getElementsByTagNameNSdocument.getElementsByTagName将表现出相同的行为。 - RBT
2
一些分析称querySelector比getElementById花费更多时间,例如在这里https://www.dimlucas.com/index.php/2016/09/17/jquery-vs-getelementbyid-vs-queryselector/。如果我们考虑访问时间会怎么样呢?从getElementById获取的实时节点是否比从querySelector获取的静态节点花费更多时间? - Eric
1
@RBT 我要提到的是,这些旧的DOM API不会返回NodeList对象,而是返回HTMLCollection。 - Miscreant
@Eric document.getElementById() 不会返回一个实时节点。它比 document.querySelector('#id_here') 更快,可能是因为 querySelector 首先需要解析 CSS 选择器。 - Miscreant

28

我来到这个页面纯粹是为了找到更好的性能方法,也就是说哪种方法更快:

querySelector / querySelectorAll or getElementsByClassName

我找到了这个链接:https://jsperf.com/getelementsbyclassname-vs-queryselectorall/18

这个链接对上面提到的两个例子进行了测试,并同时测试了jQuery等效选择器。我的测试结果如下:

getElementsByClassName = 1,138,018 operations / sec - <<< clear winner
querySelectorAll = 39,033 operations / sec
jquery select = 381,648 operations / sec

2
哇,这是一个巨大的区别,感谢您的查找。显然,“querySelectorAll”需要在幕后进行额外的工作(包括解析选择器表达式,考虑伪元素等),而“getElementsByClassName”仅仅是一个递归对象遍历。 - John Weisz

18

querySelector可以是一个完整的CSS(3)选择器,包括ID、类和伪类,就像这样:

'#id.class:pseudo'

// or

'tag #id .class .class.class'

使用 getElementsByClassName,您只需定义一个类名即可

'class'

使用 getElementById 可以仅定义一个 id。

'id'

1
:first”是一个CSS选择器吗?或许是“:first-class”或“:first-of-type”,但我认为“:first”是JavaScript/jQuery/Sizzle的附加功能。 - David Thomas
@DavidThomas 是的,它是CSS3的一部分。可以像这样使用:http://css-tricks.com/almanac/selectors/f/first-child/ - algorhythm
2
但是 :first 显然不是 :first-child - David Thomas
3
建议作者避免在传递给本规范定义的方法的选择器中使用伪元素,因为虽然选择器中允许使用伪元素,但是它们不会匹配文档中的任何元素,因此不会返回任何元素。 - rich remer
另外,在IE浏览器中存在一个错误(当然)会导致在选择伪元素时返回根HTML元素而不是空元素列表。 - rich remer
1
你错过了 getElementsByClassName 中的 s。 - Rayees AC

7

querySelectorquerySelectorAll是相对较新的API,而getElementByIdgetElementsByClassName已经存在了很长时间。这意味着你使用哪个API主要取决于需要支持哪些浏览器。

至于:,它有特殊的含义,因此如果你必须将其用作ID/类名的一部分,就必须对其进行转义。


13
并非必然如此。例如,querySelectorAll 可用于IE8,而 getElementsByClassName 则不可用。 - DaveJ
querySelectorAll...基本上包括所有东西:http://caniuse.com/#search=querySelectorAll - dsdsdsdsd
1
@Naveen 可能会对以下内容有所帮助:getelementsbyclassname vs querySelectorAll vs jquery select - lowtechsun

5

querySelector是w3c的Selector API

getElementBy是w3c的DOM API

在我看来,最显著的区别是querySelectorAll的返回类型是静态节点列表,而getElementsBy的返回类型是动态节点列表。因此,在示例2中循环永远不会结束,因为lis是动态的,并且在每次迭代期间更新自身。

// Demo 1 correct
var ul = document.querySelectorAll('ul')[0],
    lis = ul.querySelectorAll("li");
for(var i = 0; i < lis.length ; i++){
    ul.appendChild(document.createElement("li"));
}

// Demo 2 wrong
var ul = document.getElementsByTagName('ul')[0], 
    lis = ul.getElementsByTagName("li"); 
for(var i = 0; i < lis.length ; i++){
    ul.appendChild(document.createElement("li")); 
}

4

"querySelector"和"querySelectorAll"之间的区别

//querySelector returns single element
let listsingle = document.querySelector('li');
console.log(listsingle);


//querySelectorAll returns lit/array of elements
let list = document.querySelectorAll('li');
console.log(list);


//Note : output will be visible in Console
<ul>
<li class="test">ffff</li>
<li class="test">vvvv</li>
<li>dddd</li>
<li class="test">ddff</li>
</ul>


2
最初的回答

看这个

https://codepen.io/bagdaulet/pen/bzdKjL

使用getElementById比querySelector快25%

jQuery最慢

var q = time_my_script(function() {

    for (i = 0; i < 1000000; i++) {
         var w = document.querySelector('#ll');
    }

});

console.log('querySelector: '+q+'ms');

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接