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

Kev*_*Kev 89 javascript dom css-selectors

它让我觉得我document.querySelectorAll(...).map(...)甚至不能在Firefox 3.6中做到这一点,我仍然无法找到答案,所以我想我会在这篇博客上交叉发帖:

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

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

(顺便说一句,如果它只是你想要的一个功能,你可以做类似NodeList.prototype.map = Array.prototype.map;......但是,为什么这个功能(故意?)首先被阻止?)

Vla*_*den 167

您可以使用ES2015(ES6)传播运营商:

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

将StaticNodeList转换为项目数组.

以下是如何使用它的示例.

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

  • 另一种方法是使用[Array.from()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from):`Array.from(document.querySelectorAll ('div')).map(x => console.log(x.innerHTML))` (15认同)
  • 另请注意,“Array.from()”将映射函数作为其第二个参数。对于避免第二次迭代非常有用(它允许您在数组仍在构建时映射数组,因此只有一次迭代)。引用 MDN:更清楚地说,`Array.from(obj, mapFn, thisArg)` 与 `Array.from(obj).map(mapFn, thisArg)` 具有相同的结果,只是它不创建中间数组,而 `mapFn` 仅接收两个参数(元素,索引),而不接收整个数组,因为该数组仍在构建中。 (8认同)
  • 效果很好,但是这个答案是使用地图的一种奇怪的方式 (3认同)

Cre*_*esh 75

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

像" getElementsByFoo()返回有序NodeList"或" querySelectorAll()返回StaticNodeList" 这样的决策是非常有意的,因此实现不必担心基于语言相关的实现来对齐它们返回的数据结构(比如.map可以在JavaScript和Ruby中使用Arrays,但是而不是 C#中的列表.

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

John Resig 表达了与相似的选项,他补充说:

我的论点不是那么多, NodeIterator不像DOM那样,它不像JavaScript那样.它没有利用JavaScript语言中的功能并尽可能地使用它们......

我有点同情.如果DOM是专门针对JavaScript功能编写的,那么使用它会更加笨拙且更直观.同时我也了解W3C的设计决策.


mck*_*k89 42

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

Array.prototype.slice.call(document.querySelectorAll(...));
Run Code Online (Sandbox Code Playgroud)

然后你可以这样做:

Array.prototype.slice.call(document.querySelectorAll(...)).map(...);
Run Code Online (Sandbox Code Playgroud)

  • 实际上,当您更新DOM时它不会更新结果 - 因此"静态".您必须再次手动调用qSA以更新结果.但是,`slice`行为+1. (3认同)
  • 是的,就像 Kev 所说:qSA 结果集是静态的,getElementsByTagName() 结果集是动态的。 (2认同)

apf*_*ger 13

Array.from(document.querySelectorAll(...)).map(...)
Run Code Online (Sandbox Code Playgroud)

尽管https://caniuse.com/mdn-javascript_builtins_array_from在 IE11 上不可用


bob*_*nce 12

只是为了补充Crescent说的话,

如果它只是你想要的一个函数,你可以做类似NodeList.prototype.map = Array.prototype.map的事情.

不要这样做!它根本不能保证工作.

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

NodeList被允许是"主机对象"(在IE和一些旧版浏览器中是一个).这些Array方法被定义为允许在任何公开数字和length属性的JavaScript"本机对象"上操作,但它们不需要在主机对象上工作(在IE中,它们不需要).

令人讨厌的是你没有获得DOM列表上的所有数组方法(所有这些方法,而不仅仅是StaticNodeList),但是它没有可靠的方法.您必须手动将每个返回到阵列的DOM列表转换为:

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() {
    ...
});
Run Code Online (Sandbox Code Playgroud)

  • 现在它在 [Array.from()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) 中实现 (2认同)