为什么节点列表没有forEach?


91

我当时正在编写一个简短的脚本来更改<abbr>元素的内部文本,但是发现它nodelist没有forEach方法。我知道那nodelist不是继承自Array,但似乎不是forEach一种有用的方法吗?是否有一个具体的实施问题,我不知道阻止添加的forEachnodelist

注意:我知道Dojo和jQuery forEach的节点列表都有某种形式。由于限制,我无法使用任何一个。


Answers:


94

现在,NodeList在所有主要浏览器中都具有forEach()

请参阅MDN上的nodeList forEach()

原始答案

这些答案都不能解释为什么 NodeList不继承自Array,从而允许它拥有forEach其余所有内容。

答案在此es-discuss线程上找到。简而言之,它破坏了网络:

问题是代码错误地将instanceof假定为实例是与Array.prototype.concat结合使用的Array。

Google的Closure库中存在一个错误,由于该错误,几乎所有Google的应用程序都失败了。发现该库后即会对其进行更新,但在那里可能仍然存在与concat结合使用时做出相同错误假设的代码。

也就是说,某些代码做了类似的事情

if (x instanceof Array) {
  otherArray.concat(x);
} else {
  doSomethingElseWith(x);
}

但是,concat将“实际”数组(不是instanceof数组)与其他对象区别对待:

[1, 2, 3].concat([4, 5, 6]) // [1, 2, 3, 4, 5, 6]
[1, 2, 3].concat(4) // [1, 2, 3, 4]

因此,这意味着上面的代码在x是NodeList的时候就中断了,因为在它沿着doSomethingElseWith(x)路径行进之前,而之后沿着otherArray.concat(x)路径行进,由于x不是真正的数组,所以这样做很奇怪。

一段时间以来,有人提出了一个Elements类的建议,该类是Array的真正子类,将被用作“新的NodeList”。但是,至少从现在起,它已从DOM标准中删除,因为由于各种技术和规范相关的原因,尚不可行。


11
好像给我打了个坏电话。认真地说,我认为不时中断产品是正确的决定,尤其是如果这意味着我们对未来有健全的API时,尤其如此。此外,它不是像网络甚至差点被一个稳定的平台,人们习惯将其2岁的JavaScript的不再按预期..
JoyalToTheWorld

3
“破坏网络”!=“破坏Google的东西”
马特

为什么不从Array.prototype借用forEach方法呢?例如,不要在构造函数中执行this.forEach = Array.prototype.forEach而不是添加Array作为原型,甚至只是为NodeList唯一地实现forEach?
PopKernel

1
注意; 此更新NodeList now has forEach() in all major browsers似乎暗示IE不是主要的浏览器。希望对某些人来说是正确的,但对我来说还不正确。
Graham P Heath

58

你可以做

Array.prototype.forEach.call (nodeList, function (node) {

    // Your code here.

} );

3
Array.prototype.forEach.call可以[].forEach.call
缩短

7
@CodeBrauer:这不只是缩短Array.prototype.forEach.call,它还在创建一个空数组并使用其forEach方法。
Paul D. Waite

34

您可以考虑创建一个新的节点数组。

  var nodeList = document.getElementsByTagName('div'),

      nodes = Array.prototype.slice.call(nodeList,0); 

  // nodes is an array now.
  nodes.forEach(function(node){ 

       // do your stuff here.  

  });

注意:这只是我们在此处创建的节点引用的列表/数组,没有重复的节点。

  nodes[0] === nodeList[0] // will be true

22
或者只是做Array.prototype.forEach.call(nodeList, fun)
阿库恩2014年

我还建议别名forEach函数,例如:var forEach = Array.prototype.forEach.call(nodeList, callback);。现在您可以简单地致电forEach(nodeList, callback);
Andrew Craswell,2015年

19

永远不要说永远,这是2016年,并且该NodeList对象已forEach在最新的chrome(v52.0.2743.116)中实现了一种方法。

在生产中使用它还为时过早,因为其他浏览器尚不支持此功能(已测试FF 49),但我想这将很快标准化。


2
Opera也支持它,并将在Firefox v50中添加支持,该版本计划于16/11/15发行。
毛茸茸的

1
尽管已实现,但它不是任何标准的一部分。仍然最好这样做Array.prototype.slice.call(nodelist).forEach(…),这是标准的并且可以在旧的浏览器中使用。
Nate

17

简而言之,实现该方法存在设计冲突。

从MDN:

为什么我不能在节点列表上使用forEach或映射?

NodeList的用法与数组非常相似,因此很容易在其上使用Array.prototype方法。但是,这是不可能的。

JavaScript具有基于原型的继承机制。数组实例继承数组方法(例如forEach或map),因为它们的原型链如下所示:

myArray --> Array.prototype --> Object.prototype --> null (可以通过多次调用Object.getPrototypeOf获得对象的原型链)

forEach,map等是Array.prototype对象的自身属性。

与数组不同,NodeList原型链如下所示:

myNodeList --> NodeList.prototype --> Object.prototype --> null

NodeList.prototype包含item方法,但不包含Array.prototype方法,因此不能在NodeLists上使用它们。

来源:https : //developer.mozilla.org/en-US/docs/DOM/NodeList(向下滚动至为什么我不能使用forEach或在NodeList上进行映射?


8
那么,既然是清单,为什么要这样设计?什么是他们停止,使链:myNodeList --> NodeList.prototype --> Array.prototype --> Object.prototype --> null
伊戈尔·潘托维奇(IgorPantović),2014年

14

如果要在NodeList上使用forEach,只需从Array复制该函数:

NodeList.prototype.forEach = Array.prototype.forEach;

就是这样,现在您可以以与Array相同的方式使用它:

document.querySelectorAll('td').forEach(function(o){
   o.innerHTML = 'text';
});

4
除了对普通读者来说,对基类进行突变不是很明显。换句话说,在某些代码中,您必须记住对浏览器基础对象进行的每个自定义。依赖MDN文档不再有用,因为对象已更改了行为。最好在调用时显式应用原型,以便读者可以轻松地认识到forEach是借来的想法,而不是语言定义的一部分。参见上面@akuhn的答案。
Sukima

@Sukima被错误的前提所误导。在这种特定情况下,给定的方法将NodeList修复为开发人员期望的行为。这是解决系统类问题的最正确方法。(NodeList似乎尚未完成,应该在将来的语言版本中进行修复。)
Daniel Garmoshka,2016年

1
现在存在NodeList.forEach,这将成为一个非常简单的polyfill!
戴蒙'18

7

在ES2015中,您现在forEach可以对nodeList 使用method。

document.querySelectorAll('abbr').forEach( el => console.log(el));

请参阅MDN链接

但是,如果要使用HTML集合或其他类似数组的对象,则在es2015中可以使用Array.from()method。此方法采用类似数组的对象或可迭代的对象(包括nodeList,HTML Collections,字符串等),并返回一个新的Array实例。您可以像这样使用它:

const elements = document.getElementsByTagName('abbr');
Array.from(elements).forEach( el => console.log(el));

由于Array.from()方法是可调整的,因此您可以在es5代码中使用它,如下所示

var elements = document.getElementsByTagName('abbr');
Array.from(elements).forEach( function(el) {
    console.log(el);
});

有关详细信息,请参见MDN页面。

检查当前的浏览器支持

要么

es2015的另一种方法是使用传播运算符。

[...document.querySelectorAll('abbr')].forEach( el => console.log(el));

MDN点差算子

传播操作员-浏览器支持


2

我的解决方案:

//foreach for nodeList
NodeList.prototype.forEach = Array.prototype.forEach;
//foreach for HTML collection(getElementsByClassName etc.)
HTMLCollection.prototype.forEach = Array.prototype.forEach;

1
通过原型扩展DOM的功能通常不是一个好主意,尤其是在IE的较旧版本中(请参见文章)。
KFE

0

NodeList是DOM API的一部分。查看同样适用于JavaScript的ECMAScript绑定。http://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html。nodeList以及一个只读的length属性和item(index)函数返回一个节点。

答案是,您必须进行迭代。没有替代。Foreach将不起作用。我使用Java DOM API绑定,并且遇到相同的问题。


但是,是否有特定的原因为什么不应该实施呢?jQuery和Dojo都在自己的库中实现了它
Snakes和Coffee

2
但是设计冲突如何?
蛇和咖啡

0

检查MDN以获取NodeList.forEach规范。

NodeList.forEach(function(item, index, nodeList) {
    // code block here
});

在IE中,使用akuhn的答案

[].forEach.call(NodeList, function(item, index, array) {
    // code block here
});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.