使用forEach方法迭代NodeList

Ich*_*aki 6 javascript foreach nodelist slice htmlcollection

我想使用forEach方法迭代NodeList,我搜索了该方法,我发现解决方法是将NodeList转换为数组:

var nodesArray = Array.prototype.slice.call(nodeList);
nodesArray.forEach(function(node) { 
    //...
})
Run Code Online (Sandbox Code Playgroud)

但我不明白为什么我们使用这种Array.prototype.slice方法?

小智 10

迭代NodeList有很多种方法.

通常,将nodeList转换为Array或使用Array的函数来引用nodeList并不是一个好主意.

你可以使用一个好的老式for循环,从零开始循环,直到我们到达数组的末尾.这种方法已经存在,并且至今仍在使用.这种方法在某种程度上比这里提到的其他方法更不易读,但这一切都归结为更容易编写的内容.

// Convert to an array, then iterate
const nodeArray = Array.prototype.slice.call(nodeList)
nodeArray.forEach(doSomething);
Run Code Online (Sandbox Code Playgroud)

有些人会告诉你,使用向后循环将节省"计算周期",无论真正意味着什么.实际上,默认情况下,某些IDE实际上会将前一个循环转换为以下结构.

实际上,微观优化是不受欢迎的.此方法与此处提到的其他方法之间在性能上没有实际的明显差异.

// Iterate NodeList directly without conversion
Array.prototype.forEach.call(nodeList, doSomething);
Run Code Online (Sandbox Code Playgroud)

您可以使用while循环,它需要条件语句作为其参数.如果Array.prototype.slice超过NodeList的边界,它将返回null,这将结束循环.

// Perform operation on each element in NodeList, output results to a new Array
Array.prototype.map.call(nodeList, function(item) { 
    return item; 
}).forEach(doSomething);
Run Code Online (Sandbox Code Playgroud)

另一种使用for循环的方法,类似于while循环.传统for循环的中间条件需要一个条件语句,就像上面的while循环一样,所以这是有效的.

// Filter NodeList, output result to a new Array
Array.prototype.filter.call(nodeList, function(item) { 
    return /* condition */; 
}).forEach(doSomething);
Run Code Online (Sandbox Code Playgroud)

你可以使用for ... in循环Array#slice.请注意,Function#call在使用for ... in循环时必须使用它,否则它将迭代不可枚举的属性以及可枚举的属性.

Object.keys()方法返回给定对象自己的可枚举属性的数组,其顺序与for ... in循环提供的顺序相同(不同之处在于for-in循环枚举原型链中的属性为好).
来自: https ://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys

for(let i = 0; i < nodeList.length; ++i)  doSomething(nodeList[i]);
Run Code Online (Sandbox Code Playgroud)

在ES6中,您可以通过从Array()检索Iterator函数并将其应用于NodeList来使用for ... of循环.只要属性是可枚举的,这也适用于对象的大多数其他用途.

for(let i = nodeList.length; i--;)  doSomething(nodeList[i]);
Run Code Online (Sandbox Code Playgroud)

此外,足够酷,以下在ES6中工作.你在这里做的是从Array中检索Symbol.iterator函数并将其应用于NodeList对象的原型.这样,无论何时创建新的NodeList,它都将始终具有迭代器,因此您只需在脚本的开头执行此操作.

let i = 0, node;
while((node = nodeList.item(i++))) doSomething(node);
Run Code Online (Sandbox Code Playgroud)


每个前面提到的方法都有变化,并且可能还有其他方法,我没有在这里记录,但上面介绍的方法可以让您大致了解如何在不欺骗数组函数的情况下迭代NodeList.


我想使用forEach方法迭代NodeList

为什么?这是真正的问题.虽然您可以通过欺骗数组函数迭代NodeList,使其相信NodeList是一个数组,但您为什么要这样做?

我搜索了该方法,我发现解决方法是将NodeList转换为数组.

是的,这是一种劫持Array函数以迭代NodeList的方法.

但我不明白为什么我们使用切片方法?

this从原始数组返回元素的浅表副本.使用Function#call 允许我们补充NodeList#forEach我们的NodeList 的值NodeList.item(n).

Object.keys()返回一个数组,表示对象的可枚举属性的浅表副本.

有许多方法可以劫持数组函数并诱使他们认为他们正在使用数组同时为它们提供一个对象.

我个人认为以下任何方法都是可怕的代码,但是,因为您询问了它,我将包括可以在NodeList上使用的任何相关的数组函数.

let node;
for(let i = 0; (node = nodeList.item(i)); i++) doSomething(node);
Run Code Online (Sandbox Code Playgroud)
for(var i in Object.keys(nodeList))  doSomething(nodeList[i]);
Run Code Online (Sandbox Code Playgroud)

您不需要从NodeList中创建数组,只需直接使用Object.keysfor...of函数即可.

nodeList[Symbol.iterator] = [][Symbol.iterator];
for(node of nodeList) doSomething(node);
Run Code Online (Sandbox Code Playgroud)
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
for(node of nodeList) doSomething(node);
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用map根据公式从nodeList创建新数组,然后使用forEach迭代NodeList.

// Convert to an array, then iterate
const nodeArray = Array.prototype.slice.call(nodeList)
nodeArray.forEach(doSomething);
Run Code Online (Sandbox Code Playgroud)
// Iterate NodeList directly without conversion
Array.prototype.forEach.call(nodeList, doSomething);
Run Code Online (Sandbox Code Playgroud)

这些工作是因为Array是一种Object类型,NodeList与Array类似,您可以将一些Array函数与NodeList一起使用.

但是,不应该鼓励这些方法,有更好的方法来迭代NodeList


最后,如果您不想劫持任何数组函数,但想要使用forEach,则可以从NodeList构建数组.

// Perform operation on each element in NodeList, output results to a new Array
Array.prototype.map.call(nodeList, function(item) { 
    return item; 
}).forEach(doSomething);
Run Code Online (Sandbox Code Playgroud)

这有点多余,因为您基本上是迭代NodeList两次.


这里需要注意的重要一点是,就性能而言,这些方法之间并没有真正的切实区别.所有这些方法都将在一个相对类似的速率进行,如果你到了你迭代足够的元素,这点实际上很重要,你要问自己是否有更好的办法,你应该考虑解决您的问题.

至于哪种方法比其他方法更好,这一切都取决于你更容易理解的感觉以及你最舒服的写作方式.

JSPerf结果


我个人最喜欢的方法,直到ES6成为当前的标准,是while循环方法.在我看来,它是迭代NodeList最可读的方法.

// Filter NodeList, output result to a new Array
Array.prototype.filter.call(nodeList, function(item) { 
    return /* condition */; 
}).forEach(doSomething);
Run Code Online (Sandbox Code Playgroud)
for(let i = 0; i < nodeList.length; ++i)  doSomething(nodeList[i]);
Run Code Online (Sandbox Code Playgroud)