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

IT技术 javascript dom css-selectors
2021-03-11 20:12:43

document.querySelectorAll(...).map(...)即使在 Firefox 3.6中我也无法做到这一点,这让我很烦恼,而且我仍然找不到答案,所以我想我会在 SO 上交叉发布此博客中的问题:

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

有谁知道为什么你没有得到一个数组的技术原因?或者为什么StaticNodeList不会以这样的方式数组继承,你可以使用mapconcat等等?

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

6个回答

您可以使用 ES2015 (ES6)扩展运算符

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

将 StaticNodeList 转换为项目数组。

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

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

效果很好,但这个答案是使用地图的一种奇怪方式
2021-04-22 20:12:43
另一种方法是使用Array.from()Array.from(document.querySelectorAll('div')).map(x => console.log(x.innerHTML))
2021-05-14 20:12:43

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

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

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

John Resig 提出了与类似的选择他补充道

我的论点并不是很像 NodeIteratorDOM,而是不太像 JavaScript。它没有利用 JavaScript 语言中存在的功能,而是尽其所能地使用它们......

我倒是有些感同身受。如果 DOM 是专门用 JavaScript 特性编写的,那么使用起来就会少很多尴尬和直观。同时我也理解 W3C 的设计决策。

@Kev:托管变量是“主机”环境(例如 Web 浏览器)提供的任何变量。例如document,window等。IE 经常以小而微妙的方式实现这些有时不符合正常用法的“特殊”(例如作为 COM 对象),例如Array.prototype.slice.call在给出StaticNodeList;)时进行轰炸
2021-04-28 20:12:43
@Kev:我在博客文章页面上看到了您的评论,询问您将如何将 转换StaticNodeList为数组。我会认可@mck89 的答案作为将NodeList/转换StaticNodeList为本机数组的方法,但这将在 IE (8 obv) 中失败并出现 JScript 错误,因为这些对象是托管/“特殊”的。
2021-05-10 20:12:43
谢谢,这有助于我了解情况。
2021-05-12 20:12:43
没错,这就是我给他投票的原因。不过,其他人取消了我的 +1。托管/特别是什么意思?
2021-05-15 20:12:43

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

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

然后你可以这样做:

Array.prototype.slice.call(document.querySelectorAll(...)).map(...);
IE8 仅支持标准模式下的 querySelectorAll()
2021-04-28 20:12:43
实际上,当您更新 DOM 时它不会更新结果——因此是“静态”。您必须再次手动调用 qSA 以更新结果。slice不过条线+1
2021-05-06 20:12:43
是的,就像 Kev 所说:qSA 结果集是静态的,getElementsByTagName() 结果集是动态的。
2021-05-15 20:12:43

只是为了补充新月所说的,

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

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

没有 JavaScript 或 DOM/BOM 标准指定NodeList构造函数甚至作为全局/window属性存在,或者NodeList返回的 byquerySelectorAll将从它继承,或者它的原型是可写的,或者该函数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() {
    ...
});
@MarcoDemaio:不,没问题。new Array(n)只是给 JS terp 一个关于数组将结束多长时间的提示。这可能允许它提前分配该量的空间,这可能会导致加速,因为随着数组的增长可以避免一些内存重新分配。我不知道它是否真的对现代浏览器有帮助......我怀疑无法衡量。
2021-04-21 20:12:43
拍,我没想到。谢谢!
2021-05-07 20:12:43
2021-05-13 20:12:43
我同意+1。只是一个评论,我认为做“var array = []”而不是“var array = new Array(list.length)”会让代码更短。但是,如果您知道这样做可能会出现问题,我很感兴趣。
2021-05-20 20:12:43
Array.from(document.querySelectorAll(...)).map(...)

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

虽然这可能不能完全回答OP 提出为什么问题......它仍然是 OP 面临的问题的有效、务实和简洁的解决方案,并且对我也很有效。许多其他帖子谈论覆盖Element原型这是危险的。我会改用这个解决方案。
2021-05-15 20:12:43