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

即使在 Firefox 3.6中,我也不能仅仅使用 document.querySelectorAll(...).map(...),这让我很困扰,而且我仍然找不到答案,所以我想我应该交叉发布这个博客中的问题:

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

有人知道为什么没有数组的技术原因吗?或者,为什么 StaticNodeList 不能从 Array 继承,以便您可以使用 mapconcat等?

(顺便说一句,如果它只是你想要的一个函数,你可以做类似于 NodeList.prototype.map = Array.prototype.map;的事情... ... 但是,为什么这个功能(有意为之?)一开始就被屏蔽了?)

72771 次浏览

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

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

然后你可以做:

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

我认为这是 W3C 的一个哲学决定。W3C DOM [ spec ]的设计与 JavaScript 的设计是完全正交的,因为 DOM 是平台和语言中立的 意思是

像“ getElementsByFoo()返回一个有序的 NodeList”或者“ querySelectorAll()返回一个 StaticNodeList”这样的决定是有意为之的,因此实现不必担心根据语言相关的实现对返回的数据结构进行调整(比如在 JavaScript 和 Ruby 的 Array 上可以使用 .map,但在 C # 的 List 上可以使用 没有)。

W3C 的目标很低: 他们会说一个 NodeList应该包含一个 无符号 long 类型的 readonly .length属性,因为他们相信每个实现至少可以支持 那个,但是他们不会明确地说 []索引操作符应该被重载以支持位置元素,因为他们不想阻碍一些想要实现 getElementsByFoo()但不能支持运算符重载的可怜的小语言。这是一个普遍的哲学存在于规范的大部分。

约翰 · 雷西格的 表达了类似的选择和你的一样,他补充道:

我的论点并不是 NodeIterator不太像 DOM 它不太像 JavaScript 没有利用这些特征 呈现在 JavaScript 语言和 尽其所能地利用它们。

我确实有点同情。如果 DOM 是专门根据 JavaScript 特性编写的,那么使用起来就不会那么笨拙,也更直观。同时,我也理解 W3C 的设计决策。

我想补充一下新月的话,

如果只需要一个函数,那么可以执行 NodeList.Prototype.map = Array.Prototype.map 之类的操作

不要这样做! 这根本不能保证有效。

没有任何 JavaScript 或 DOM/BOM 标准规定 NodeList构造函数甚至作为全局/window属性存在,或者 querySelectorAll返回的 NodeList将继承它,或者它的原型是可写的,或者函数 Array.prototype.map将在 NodeList 上实际工作。

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() {
...
});

你可使用 ES2015(ES6) 扩散算符扩散算符:

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

将 StaticNodeList 转换为项的 Array。

这里有一个关于如何使用它的例子。

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

我觉得你可以简单地跟着做

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

对我来说很完美

这是一个选项,我想添加到这里的其他人建议的其他可能性的范围。它的意思是只有智力的乐趣,是 不建议


只是为了它的 有趣,这里有一个方法来“强迫”querySelectorAll跪下并向你鞠躬:

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

现在,跨过这个函数,让它知道谁是老板,感觉很好。 现在我不知道什么更好,创建一个全新的 名字函数包装器,然后让你所有的代码使用那个奇怪的名称(几乎是 jQuery 风格的)或者像上面那样覆盖函数一次,这样你剩下的代码仍然可以使用原来的 DOM 方法名称 querySelectorAll

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

我不会以任何方式推荐这种做法,除非你真的不在乎(你知道的)。

Array.from(document.querySelectorAll(...)).map(...)

在 IE11和 https://caniuse.com/mdn-javascript_builtins_array_from上都不可用