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

Posted

技术标签:

【中文标题】为啥 document.querySelectorAll 返回的是 StaticNodeList 而不是真正的 Array?【英文标题】:Why does document.querySelectorAll return a StaticNodeList rather than a real Array?为什么 document.querySelectorAll 返回的是 StaticNodeList 而不是真正的 Array? 【发布时间】:2011-02-05 17:15:57 【问题描述】:

让我感到困扰的是,即使在 Firefox 3.6 中我也不能只做 document.querySelectorAll(...).map(...),我仍然找不到答案,所以我想我会在这个博客上交叉发布这个问题:

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

有没有人知道您没有获得 Array 的技术原因?或者为什么 StaticNodeList 不能以您可以使用 mapconcat 等的方式从 Array 继承?

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

【问题讨论】:

实际上 getElementsByTagName 也不返回一个数组,而是一个集合,如果你想像数组一样使用它(使用 concat 等方法),你必须通过这样做将这样的集合转换为数组一个循环并将集合的每个元素复制到一个数组中。没有人对此抱怨过。 【参考方案1】:

你可以使用 ES2015 (ES6) spread operator:

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

将静态节点列表转换为项目数组。

这是一个如何使用它的示例。

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

【讨论】:

另一种方式是使用Array.from():Array.from(document.querySelectorAll('div')).map(x =&gt; console.log(x.innerHTML)) 效果很好,但这个答案是使用地图的一种奇怪方式【参考方案2】:

我相信这是 W3C 的哲学决定。 W3C DOM [规范] 的设计与 javascript 的设计完全正交,因为 DOM 意味着是平台和语言中立的。

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

W3C 的目标很低:他们会说 NodeList 应该包含 readonly .length property of type unsigned long,因为他们相信每个实现都至少可以支持 那个,但他们不会明确表示 @ 987654331@ 索引运算符应该被重载以支持获取位置元素,因为他们不想阻碍一些想要实现getElementsByFoo() 但不能支持运算符重载的可怜的小语言。这是贯穿大部分规范的普遍理念。

John Resig 拥有 voiced a similar option 是你的,he adds:

我的论点并非如此 NodeIterator 不是很像 DOM 它不是很像 JavaScript。它 没有利用这些功能 存在于 JavaScript 语言中,并且 尽其所能使用它们...

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

【讨论】:

谢谢,这有助于我理解情况。 @Kev:我看到您对该博客文章页面的评论询问您将如何将StaticNodeList 转换为数组。我赞同@mck89 的答案作为将NodeList/StaticNodeList 转换为本机数组的方法,但是在 IE(8 obv)中会失败并出现 JScript 错误,因为这些对象是托管的/“特殊的” . 没错,这就是我支持他的原因。不过,其他人取消了我的 +1。托管/特殊是什么意思? @Kev:托管变量是“宿主”环境(例如网络浏览器)提供的任何变量。例如documentwindow等。IE经常以小而微妙的方式实现这些有时不符合正常用法的“特殊”(例如作为COM对象),例如Array.prototype.slice.call给定时的轰炸StaticNodeList ;)【参考方案3】:

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

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

然后你可以这样做:

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

【讨论】:

实际上,当您更新 DOM 时它不会更新结果 - 因此是“静态”。您必须再次手动调用 qSA 才能更新结果。为slice 行 +1。 是的,就像 Kev 说的:qSA 结果集是静态的,getElementsByTagName() 结果集是动态的。 IE8 仅支持标准模式下的 querySelectorAll()【参考方案4】:

只是为了补充 Crescent 所说的,

如果它只是你想要的一个功能,你可以做类似 NodeList.prototype.map = Array.prototype.map

不要这样做!根本不保证会起作用。

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

NodeList 可以是一个“宿主对象”(在 IE 和一些旧浏览器中是一个)。 Array 方法被定义为允许对任何公开数字和 length 属性的 JavaScript 'native object' 进行操作,但它们不需要在主机对象上工作(在 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() 
    ...
);

【讨论】:

开枪,我没想到。谢谢! 我同意 +1。只是一个评论,我认为做“var array = []”而不是“var array = new Array(list.length)”会使代码更短。但如果您知道这样做可能会出现问题,我很感兴趣。 @MarcoDemaio:不,没问题。 new Array(n) 只是给 JS terp 一个关于数组将结束多长时间的提示。这可能允许它提前分配该数量的空间,这可能会导致加速,因为随着数组的增长可以避免一些内存重新分配。我不知道它在现代浏览器中是否真的有帮助......我怀疑无法衡量。 现在在Array.from()实现【参考方案5】:
Array.from(document.querySelectorAll(...)).map(...)

虽然https://caniuse.com/mdn-javascript_builtins_array_from在 IE11 上不可用

【讨论】:

虽然这可能不能完全回答 OP 提出的 why 问题......但它仍然是 OP 面临的问题的有效、务实和简洁的解决方案,并且运行良好对我来说也是。许多其他帖子都谈到了覆盖Element 原型,这很危险。我会改用这个解决方案。【参考方案6】:

我认为您可以简单地进行以下操作

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

它非常适合我

【讨论】:

【参考方案7】:

这是我想添加到其他人在此处建议的其他可能性范围中的一个选项。它仅用于智力乐趣,不建议


为了它的乐趣,这里有一种方法可以“强迫”querySelectorAll 跪下来向你鞠躬:

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

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

这种方法将消除可能使用sub-methods

我不会以任何方式推荐这个,除非你真的不给[你知道什么]。

【讨论】:

以上是关于为啥 document.querySelectorAll 返回的是 StaticNodeList 而不是真正的 Array?的主要内容,如果未能解决你的问题,请参考以下文章

“document.querySelector(...)为空”错误[重复]

document.querySelector()方法document.querySelectorA()

document.querySelector()和document.querySelectorAll()

如何将条件反应与 document.querySelector 关联?

querySelector选择器

Document.querySelector() 未显示所有元素