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

Why does document.querySelectorAll return a StaticNodeList rather than a real Array?

令我感到困扰的是,即使在Firefox 3.6中,我也不能只执行document.querySelectorAll(...).map(...),而我仍然找不到答案,所以我认为我会交叉张贴此博客中的问题:

Yay for querySelectorAll. Boo for StaticNodeList.

有人知道您没有获得阵列的技术原因吗? 还是为什么StaticNodeList不能以可以使用mapconcat等的方式从数组继承?

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


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

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

会将StaticNodeList转换为项目数组。

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

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


我相信这是W3C的哲学决定。 W3C DOM [spec]的设计与JavaScript的设计非常正交,因为DOM是平台和语言无关的。

诸如" getElementsByFoo()返回有序的NodeList"或" querySelectorAll()返回一个StaticNodeList"之类的决策非常有意,因此实现无需担心基于语言依赖的实现对齐返回的数据结构(例如.map在JavaScript和Ruby中可用于数组,但在C#中不可用于列表)。

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

约翰·雷西格(John Resig)提出了与您类似的选项,他补充说:

My argument isn't so much that
NodeIterator isn't very DOM-like it's
that it isn't very JavaScript-like. It
doesn't take advantage of the features
present in the JavaScript language and
use them to the best of its ability...

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


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

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

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

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


只是要加上Crescent所说的话,

if it's just one function you want, you can do something like NodeList.prototype.map = Array.prototype.map

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

没有JavaScript或DOM / BOM标准指定NodeList构造函数甚至作为global / window属性存在,或者querySelectorAll返回的NodeList将从其继承,或者其原型可写,或函数Array.prototype.map实际上将在NodeList上运行。

NodeList可以是"主机对象"(在IE和某些旧版浏览器中是一个)。 Array方法被定义为允许在任何显示数字和length属性的JavaScript"本机对象"上运行,但不需要在宿主对象上使用(在IE中则不需要)。

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

1
2
3
4
5
6
7
8
9
10
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
Array.prototype.map.call(document.querySelectorAll(...), function(...){...});

它对我来说很完美


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

只是为了好玩,这是一种"强制" querySelectorAll下跪并向您鞠躬的方法:

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

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

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

I wouldn't recommend this in any way, unless you honestly don't give a [you know what].