为什么document.querySelectorAll返回StaticNodeList而不是真正的Array?


103

令我感到困扰的是,document.querySelectorAll(...).map(...)即使在Firefox 3.6 中我也无法做到这一点,而且仍然找不到答案,所以我认为我应该交叉张贴此博客中的问题:

http://blowery.org/2008/08/29/yay-for-queryselectorall-boo-for-staticnodelist/

有人知道您没有获得阵列的技术原因吗?或者为什么StaticNodeList不会以这样的方式数组继承,你可以使用mapconcat等等?

(顺便说一句,如果它只是您想要的一个功能,您可以执行类似NodeList.prototype.map = Array.prototype.map;...的操作,但是再次,为什么首先有意阻止此功能?)


3
实际上,getElementsByTagName也不返回Array,而是一个集合,并且,如果您想像Array一样使用它(使用诸如concat等方法),则必须通过执行循环并将该集合转换为Array来复制它的每个元素。集合成一个数组。没人抱怨过这一点。
Marco Demaio 2010年

Answers:


81

我相信这是W3C的哲学决定。在W3C DOM [规格]的设计是相当正交的JavaScript的设计,作为DOM的意思是平台和语言无关。

诸如“ getElementsByFoo()返回有序NodeList”或“ querySelectorAll()返回a StaticNodeList”之类的决定非常有意,因此实现不必担心基于语言相关的实现来调整返回的数据结构(例如.map可用于JavaScript和Ruby中的Arrays,但是不在 C#列表中)。

W3C的目标很低:他们会说a NodeList应该包含一个unsigned long类型只读.length属性,因为他们相信每个实现都至少可以支持,但是他们不会明确表示[]应该重载索引运算符以支持获取位置元素,因为他们不想阻挠要实现getElementsByFoo()但不能支持运算符重载的一些可怜的小语言。这是整个规范中普遍存在的哲学。

约翰·雷西格(John Resig)表示了与类似的选项他补充说

我的论点不是很像NodeIteratorDOM , 也不是像JavaScript。它没有利用JavaScript语言中提供的功能,而是最大限度地利用它们。

我有点同情。如果DOM是专门考虑JavaScript功能编写的,那么它就不会那么笨拙并且使用起来更加直观。同时,我确实了解W3C的设计决策。


谢谢,这可以帮助我了解情况。
凯夫

@Kev:我在博客文章页面上看到您的评论,质疑您如何将转换StaticNodeList为数组。我将@ mck89的答案作为将NodeList/ 转换StaticNodeList为本机Array的方法,但在IE(8 obv)中,它会因JScript错误而失败,因为这些对象是托管的/“特殊的”。
Crescent Fresh

没错,这就是为什么我投票给他。有人取消了我的+1。您所说的托管/特殊是什么意思?
凯夫

1
@Kev:托管变量是“主机”环境(例如Web浏览器)提供的任何变量。例如documentwindow等等。IE通常会以小巧的方式实现有时不符合正常用法的“特殊”(例如,作为COM对象),例如Array.prototype.slice.call在给以炸弹的情况下StaticNodeList;)
Crescent Fresh

199

您可以使用ES2015(ES6)传播算子

[...document.querySelectorAll('div')]

会将StaticNodeList转换为项目数组。

这是有关如何使用它的示例。

[...document.querySelectorAll('div')].map(x => console.log(x.innerHTML))
<div>Text 1</div>
<div>Text 2</div>


24
另一种方法是使用Array.from()Array.from(document.querySelectorAll('div')).map(x => console.log(x.innerHTML))
Michael Berdyshev

42

我不知道为什么它返回一个节点列表而不是一个数组,也许是因为像getElementsByTagName一样,它会在更新DOM时更新结果。无论如何,将结果转换为简单数组的一种非常简单的方法是:

Array.prototype.slice.call(document.querySelectorAll(...));

然后您可以执行以下操作:

Array.prototype.slice.call(document.querySelectorAll(...)).map(...);

3
实际上,当您更新DOM时,它不会更新结果-因此为“静态”。您必须再次手动调用qSA来更新结果。slice线路虽然+1 。
凯夫

1
是的,就像Kev所说的那样:qSA结果集是静态的,getElementsByTagName()结果集是动态的。
joonas.fi,2015年

IE8仅在标准模式下支持querySelectorAll()
mbokil 2016年

13

只是要加上Crescent所说的话,

如果这只是您想要的一个功能,则可以执行类似NodeList.prototype.map = Array.prototype.map的操作

不要这样!根本不能保证它能正常工作。

没有的JavaScript或DOM / BOM标准规定,NodeList构造函数,即使存在作为全球/ window属性,或者说,NodeList通过返回querySelectorAll将继承它,或者它的原型是可写的,或者该功能Array.prototype.map实际上会在一个节点列表的工作。

NodeList可以作为“主机对象”(在IE和某些旧版浏览器中是一个)。这些Array方法被定义为可以在任何公开数字和length属性的JavaScript“本机对象”上运行,但不需要在宿主对象上运行(在IE中则不需要)。

令人讨厌的是,您没有在DOM列表上获得所有的数组方法(所有方法,而不仅仅是StaticNodeList),但是没有可靠的解决方法。您必须将每个返回的DOM列表手动转换为Array:

Array.fromList= function(list) {
    var array= new Array(list.length);
    for (var i= 0, n= list.length; i<n; i++)
        array[i]= list[i];
    return array;
};

Array.fromList(element.childNodes).forEach(function() {
    ...
});

1
开枪,我没想到。谢谢!
Kev

我同意+1。只是一句评论,我认为用“ var array = []”代替“ var array = new Array(list.length)”会使代码更短。但是,如果您知道这样做可能会有问题,我很感兴趣。
Marco Demaio 2010年

@MarcoDemaio:不,没问题。new Array(n)只是给JS terp一个提示,说明数组将结束多长时间。这可以允许它预先分配一定数量的空间,这有可能导致加速,因为随着数组的增长可以避免某些内存的重新分配。我不知道它是否实际上对现代浏览器有帮助...我怀疑无法衡量。
bobince 2010年

2
现在,它已在Array.from()
Michael Berdyshev,

2

我认为您可以按照以下步骤进行操作

Array.prototype.map.call(document.querySelectorAll(...), function(...){...});

对我来说很完美


0

我想将此选项添加到此处其他人建议的其他可能性范围中。它仅出于智力娱乐目的,不建议使用


只是为了好玩,这是一种“强迫” querySelectorAll跪下并向你鞠躬的方法:

Element.prototype.querySelectorAll = (function(QSA){
    return function(){
        return [...QSA.call(this, arguments[0])]
    }
})(Element.prototype.querySelectorAll);

现在,遍历该功能感觉很好,向其显示谁是老板。现在,我不知道有什么更好的方法,创建一个全新的命名函数包装器,然后让您的所有代码使用该古怪的名称(几乎是jQuery样式),或者像上面那样重写该函数一次,以便其余代码仍然能够使用原始DOM方法名称querySelectorAll

  • 这种方法将消除可能使用的子方法

我不会以任何方式推荐此方法,除非您老实说不给[您知道什么]。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.