Iterate a NodeList using forEach method

2019-02-10 06:42发布

I want to iterate a NodeList using forEach method, I googled about the method, and I found the solution to do that is to convert the NodeList to an Array :

var nodesArray = Array.prototype.slice.call(nodeList);

But I don't understand why we used the slice method ?

4条回答
叛逆
2楼-- · 2019-02-10 06:56

Because slice returns a copy of any array-like argument as a new array object, which is exactly what we need. We could just as easily use concat.

查看更多
贼婆χ
3楼-- · 2019-02-10 07:03

Iterate a NodeList using forEach method

But I don't understand why we used the slice method ?

You don't have to, you could do this directly

Array.prototype.forEach.call(nodelist, function(value, index) {
    ...
});
查看更多
在下西门庆
4楼-- · 2019-02-10 07:05

There are many ways to iterate a NodeList.

In general, it is not a good idea to turn a nodeList into an Array or to use an Array's functions in reference to a nodeList.

You can use a good old fashioned for loop, start at zero and loop until we reach the end of the array. This method has been around for ever and is still used regularly today. This method is, somewhat, less readable than other methods mentioned here, but it all comes down to what is easier for you to write.

for(var i = 0; i < nodeList.length; ++i)  doSomething(nodeList[i]);

Some people will tell you that using a backwards for loop will save "computation cycles", whatever that really means. In fact, some IDEs will actually convert the previous loop to the following structure by default.

In reality, micro-optimization is frowned upon. There is no real tangible difference in performance between this method and the other methods mentioned here.

for(var i = nodeList.length - 1; i > -1; --i)  doSomething(nodeList[i]);

You can use a while loop, which expects a conditional statement as its parameter. If NodeList.item(n) is past the bounds of the NodeList it will return null, which will end the loop.

var i = 0;
while((node = nodeList.item(i++))) doSomething(node);

Another method of using a for loop, which is similar to the while loop. The middle condition of a traditional for loop expects a conditional statement, just like the while loop above, so this works.

for(var i = 0; (node = nodeList.item(i)); i++) doSomething(node);

You can use a for...in loop with Object.keys(). Note that you have to use Object.keys when using a for...in loop because otherwise it will iterate over the non-enumerable properties as well as the enumerable ones.

The Object.keys() method returns an array of a given object's own enumerable properties, in the same order as that provided by a for...in loop (the difference being that a for-in loop enumerates properties in the prototype chain as well).
From: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys

for(var i in Object.keys(nodeList))  doSomething(nodeList[i]);

In ES6, you can use a for...of loop by retrieving the Iterator function from Array() and applying it to the NodeList. This will work for most other uses of an object as well, as long as the properties are enumerable.

nodeList[Symbol.iterator] = [][Symbol.iterator];
for(node of nodeList) doSomething(node);

Also, cool enough, the following works in ES6. What you're doing here is retrieving the Symbol.iterator function from the Array and applying that to the NodeList object's prototype. This way, whenever a new NodeList is created, it will always have the iterator, so you only have to do that at the beginning of the script.

(function(){
    "use strict";
    NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
    for(let node of document.querySelectorAll('*')) document.body.innerHTML += node.tagName + ' ';
    document.body.innerHTML += '<br>';
    for(let node of document.querySelectorAll('*:not(body)')) document.body.innerHTML += node.tagName + ' ';
})();


There are variations on each of the previously mentioned methods as well as there are probably other methods that I haven't documented here, but the methods documented above give you the general idea of how you can iterate a NodeList without tricking array functions.


I want to iterate a NodeList using forEach method

Why? That is the real issue here. While you can iterate a NodeList by tricking an array function into believing that a NodeList is an Array, why would you want to?

I googled about the method, and I found the solution to do that is to convert the NodeList to an Array.

Yes, that is one method of hijacking an Array function in order to iterate a NodeList.

But I don't understand why we used the slice method?

Array.prototype.slice() returns a shallow copy of elements from the original array. Using .call() allows us to supplement the value of this to our NodeList for slice().

Array.prototype.slice.call(NodeList) returns an array representing a shallow copy of the object's enumerable properties.

There are numerous methods of hijacking Array functions and tricking them into thinking that they are working with an Array while feeding them an Object.

I personally believe that any of the following methods are horrible code but, because you asked about it, I will include any relevant array functions that can be used on a NodeList.

Array.prototype.slice.call(nodeList).forEach(doSomething);
[].slice.call(nodeList).forEach(doSomething);

You don't need to make an array out of the NodeList, you can simply use .call() on the forEach function directly.

Array.prototype.forEach.call(nodeList, doSomething);
[].forEach.call(nodeList, doSomething);

Or you can use map to create a new array from the nodeList based on a formula, then iterate the NodeList using forEach.

Array.prototype.map.call(nodeList, function(item) { 
    return item; 
}).forEach(doSomething);
[].map.call(nodeList, function(item) { 
    return item; 
}).forEach(doSomething);

These work because an Array is a type of Object, and a NodeList is similar enough to an Array that you can use some of the Array functions with a NodeList.

However, these methods should not be encouraged, there are much better ways to iterate a NodeList


Finally, if you don't want to hijack any array functions, but do want to use forEach, you can build an array from the NodeList.

var array = new Array(els.length);
for(var key in Object.keys(nodeList)) arrary[key] = nodeList[key];
array.forEach(doSomething);

This is somewhat redundant because you are basically iterating the NodeList twice.


The important thing to note here, is that there is no real tangible difference between any of these methods as far as performance is concerned. All of these methods will perform at a relatively similar rate and if you get to a point where you are iterating over enough elements that it would actually matter, you should ask yourself if there is better approach that you should be taking to solve your problem.

As for which method is better than the rest, it all depends on what you feel is more easily understood and what you feel most comfortable writing.

JSPerf results


My personal favorite method at the moment, until ES6 becomes the current standard, is the while loop method. In my opinion it is the most readable method of iterating a NodeList.

var els = document.getElementsByTagName('i'), i = 0, el;

while((el = els.item(i++))) el.innerHTML += '*';
<i>Hello</i> <i>foo</i> <i>bar</i> <i>world</i>

查看更多
▲ chillily
5楼-- · 2019-02-10 07:10

All these answers are outdated. Actually you can use forEach on NodeList in modern browsers!

So just use forEach!

查看更多
登录 后发表回答