为什么我不能在JavaScript元素集合上使用Array.forEach?

20

我正在使用Babel/ES6构建一个应用程序。 我想为其创建一个仅查看版本,并禁用所有表单元素,因此我执行了以下操作:

let form = document.getElementById('application-form')
let elements = form.elements

我本来期望可以这样做,而不是使用普通的 for 循环(虽然它也可以工作):

elements.forEach((el) => {
    el.disabled = true
})

但我得到了 TypeError: elements.forEach is not a function

奇怪的是,如果我在Chrome开发者控制台中使用console.log(elements),它看起来像一组input对象。它不会以Object表示法显示为对象,并且所有键都是整数。我想这是某种伪数组,但我甚至不知道如何找出答案。

编辑:简短的回答是这不是一个数组,而是HTMLCollection。请参见为什么节点列表没有forEach?


*更新*

根据这个答案nodelist现在有了forEach方法!


console.log(typeof elements) 的结果是什么? - Hackerman
3
高端写法:使用[...elements].forEach。 (翻译者注:该写法是将类数组对象转化为真正的数组,并调用forEach方法对每个元素执行相同的操作。) - Sterling Archer
object。但在开发控制台中看起来像一个数组。看起来实际上是一个HTMLCollection。 - inostia
我认为重新打开并不会阻止关闭。有一次我的教条主义引擎非常高涨,所以我对你做了这件事。当我点击时,我没有检查它是否已经重新打开,所以可能是我落后于你。无论如何,我同意这是一个不错的 CW 候选者。 - user1106925
1
@squint: “……当我的吹毛求疵引擎高速运转时……” 哈哈哈!我肯定完全不知道那是什么感觉。 - T.J. Crowder
显示剩余2条评论
3个回答

36
你可以使用它,但不能像那样使用,因为HTMLFormControlsCollection没有forEach属性,而form.elements提供的不是数组。

以下是如何仍然可以使用它:

Array.prototype.forEach.call(form.elements, (element) => {
    // ...
});

或者在现代浏览器中,您可以利用底层的 HTMLCollection 是可迭代的事实,即使它没有 forEach 方法:

// `for-of`:
for (const element of elements) {
    // ...do something with `element`...
}

// Spreading into an array:
[...elements].forEach((element) => {
    // ...do something with `element`...
});

尽管如此,这在IE11等过时的浏览器上不起作用。

还有Array.from(需要为过时的浏览器提供polyfill):

Array.from(elements).forEach((element) => {
    // ...
});

详细信息请参见我的答案中的"类似数组"部分。


2
ES6 转换:[...elements].forEach(el => el.disabled = true); - Sterling Archer
1
@SterlingArcher:那个和.from()解决方案都可以,但它们不必要地创建了一个新的数组并复制了节点。可惜Firefox的数组通用方法没有被标准化。使用Array.forEach(elements, function(el) {...})更加优雅。 - user1106925
2
@T.J.Crowder,实际上那对我没用。 [...elements].forEach 在 WebPack 中给了我一个意外的标记错误。但是 let elements = [...form.elements]; elements.forEach 是有效的。 - inostia
@inostia: !!! 很好的发现,spread 只能用于可迭代对象!并非 Array.from 能转换为数组的所有内容都是可迭代的!有趣的是,querySelectorAll 的结果似乎是可迭代的(在 Chrome 上),并且可以很好地与 spread 符号一起使用。 - T.J. Crowder
@inostia: 不好意思,我看错了,你当然是说过的。但请注意,你在这里依赖于可迭代性。需要检查相关规范以确保这一点得到保证(无论如何,你会在目标浏览器中进行测试,并且转译将执行类似于Array.from的操作)。 - T.J. Crowder
显示剩余11条评论

3
您不能在HTMLCollection上使用forEach。forEach只能用于数组。
替代方法是使用lodash并执行_.toArray(),这将把HTMLColelction转换为数组。之后,您可以对其进行正常的数组操作。或者,使用ES6 spread并执行forEach()。
像这样:
var a = document.getElementsByTagName('div')
[...a].forEach()

0

form.elementsdocument.getElementsByTagNamedocument.getElementsByClassNamedocument.querySelectorAll 返回一个节点列表。

节点列表本质上是一个没有任何方法的对象,就像一个数组。

如果您希望将节点列表用作数组,可以使用 Array.from(NodeList) 或老式的 Array.prototype.slice.call(NodeList)

// es6
const thingsNodeList = document.querySelectorAll('.thing')
const thingsArray = Array.from(thingsNodeList)
thingsArray.forEach(thing => console.log(thing.innerHTML))

// es5
var oldThingsNodeList = document.getElementsByClassName('thing')
var oldThingsArray = Array.prototype.slice.call(oldThingsNodeList)
thingsArray.forEach(function(thing){ 
  console.log(thing.innerHTML) 
})
<div class="thing">one</div>
<div class="thing">two</div>
<div class="thing">three</div>
<div class="thing">four</div>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接