为啥 forEach 不适用于儿童?

Posted

技术标签:

【中文标题】为啥 forEach 不适用于儿童?【英文标题】:Why is forEach not working for children?为什么 forEach 不适用于儿童? 【发布时间】:2013-02-12 05:52:06 【问题描述】:

我有一个<div>,里面有一些孩子<div>。例如

<div id="niceParent">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</div>

我尝试使用forEach 函数循环遍历它们,因为我认为document.getElementById("niceParent").children 是一个数组,因为我可以使用

console.log(document.getElementById("niceParent").children[1]);
console.log(document.getElementById("niceParent").children[2]);
console.log(document.getElementById("niceParent").children[3]);
console.log(document.getElementById("niceParent").children[4]);

所以我尝试了

document.getElementById("niceParent").children.forEach(function(entry) 
  console.log(entry);
);

这不起作用。我明白了

TypeError: document.getElementById(...).children.forEach is not a function

作为一种解决方法,我还尝试了一个更复杂的for..in 循环:

for (var i in document.getElementById("niceParent").children) 
  if (document.getElementById("niceParent").children[i].nodeType == 1) console.log(document.getElementById("niceParent").children[i]);

按预期工作。

为什么?

【问题讨论】:

【参考方案1】:

因为.children 包含htmlCollection [MDN],而不是数组。 HTMLCollection 对象是一个 array-like 对象,它公开了一个 .length 属性并具有数字属性,只是 like 数组,但它不继承自 @987654330 @ 因而不是一个数组。

您可以使用Array.prototype.slice 将其转换为数组:

var children = [].slice.call(document.getElementById(...).children);

ECMAScript 6 引入了一个新的 API,用于将迭代器和类似数组的对象转换为真正的数组:Array.from [MDN]。尽可能使用它,因为它使意图更加清晰。

var children = Array.from(document.getElementById(...).children);

【讨论】:

我愿意接受 Felix Kling 和 lonesomeday 的回答,因为尽管使用了一些别名,但它们是相同的。谢谢你。尤其是指向 MDN 的链接。 在 ECMAScript6 中,可以使用Array.prototype.from() 以更清晰的方式说明转换为数组的意图。 虽然 Array.from 或 slice 在技术上是可行的,但与简单地使用 for 循环遍历原始 NodeList 相比,所涉及的转换过程肯定会大大降低效率吗? 实际上.children 包含HTMLCollection,而不是NodeListNodeList 现在确实有 forEach 方法,而 HTMLCollection 没有。 借助扩展运算符,您可以使用 ES6 做得更好。在下面查看我的answer :)【参考方案2】:

Element.children不是数组。它是一个名为HTMLCollection 的对象。它们没有数组的方法(尽管它们有 length 属性)。

要遍历它,您必须将其转换为数组,您可以使用Array.prototype.slice

var children = Array.prototype.slice.call(document.getElementById("niceParent").children);

children.forEach(…);

【讨论】:

【参考方案3】:

HTMLCollection(如.children)转换为数组以使用forEach()(或map()等)的更简洁、更现代的方法是在数组[]中使用spread synthax ...

var children = [...document.getElementById('x').children];

例如:

[...document.getElementById('x').children].forEach(child => console.log(child))

这是一个 es6 功能。它适用于所有现代浏览器。

[...document.getElementById('niceParent').children].forEach(child =&gt; console.log(child.textContent))
<div id="niceParent">
  <div>a</div>
  <div>b</div>
  <div>c</div>
  <div>d</div>
</div>

【讨论】:

【参考方案4】:

你也可以这样做:

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

在此之后,您可以在您的集合上调用 forEach:

document.getElementById("niceParent").children.forEach(...)

最好和最安全的方法实际上是只在 forEach 不存在的情况下添加它:

if (window.NodeList && !NodeList.prototype.forEach) 
   NodeList.prototype.forEach = Array.prototype.forEach;

if (window.HTMLCollection && !HTMLCollection.prototype.forEach) 
   HTMLCollection.prototype.forEach = Array.prototype.forEach;

【讨论】:

但是,如果与for..in 方法结合使用,则会造成困难。 推荐阅读文章:"What’s wrong with extending the DOM". 尽管如此,知道这是可能的还是很有趣的。 不要这样做。如果您必须扩展原生原型,请首先检查它们的存在,然后将add a non-enumerable, non-constructible method 发送到原型。作为参考,NodeList.prototype.forEach 存在于最近的浏览器中。 在我看来,用一个好的 polyfill 扩展 DOM 并不是一个坏习惯。我会推荐这个答案。 @SebastianSimon 您链接的 MDN 页面将此答案列为 Polyfill:“上述行为是有多少浏览器实际实现 NodeList.prototype.forEach”【参考方案5】:

如果您需要使用轻量级 npm 模块的干净方法来解决上述问题,请查看https://www.npmjs.com/package/foreach-array

例如:

import each from 'foreach-array';

const array = ['First Name', 'Last Name', 'Country'];

each(array, (value, index, array) => 
    console.log(index + ': ' + value);
);

// Console log output will be:
//      0: First Name
//      1: Last Name
//      2: Country

对于您的场景,它是document.getElementById("niceParent").children,而不是上面示例中的array

【讨论】:

【参考方案6】:

也许这是一个简单的解决方案:

document.getElementById("niceParent").childNodes.forEach(function(entry) 
  console.log(entry);
);

【讨论】:

以上是关于为啥 forEach 不适用于儿童?的主要内容,如果未能解决你的问题,请参考以下文章

并行化不适用于 foreach 包

Foreach 不适用于两个数组

[SwiftUI]:ForEach 不适用于字典数组、带数组的字典

向 div 添加事件监听器适用于 ForEach() 方法,但不适用于 for 循环。有啥不同?

SwiftUI 中的 Picker 适用于 ForEach 的一个版本,但不适用于另一个版本 - 错误或预期行为?

Java中foreach为啥不能给数组赋值