What is the purpose of calling Array.prototype.slice
against a NodeList?
The Array#slice method "returns a shallow copy of a portion of an array into a new array object".
The Function#call method "calls a function with a given this value and arguments provided individually".
Because Arrays are Objects, all Object property names are stored as strings, and all NodeLists store their elements with sequential numbered property names (again stored as strings), NodeLists can be used as the this
value for Array methods.
Creating a shallow copy of the NodeList as an Array allows you to use other Array methods on the newly created Array without using Function#call
.
Many modern browsers have implemented NodeList#forEach, though the method is still a candidate recommendation and has not made it into the specification at this point. I recommend using that method with care, and not on the open web, until it has reached a more stable status.
Here are some other examples of Array methods being called with a NodeList as the target:
// Convert to an array, then iterate
const nodeArray = Array.prototype.slice.call(nodeList)
nodeArray.forEach(doSomething);
// Iterate NodeList directly without conversion
Array.prototype.forEach.call(nodeList, doSomething);
// Perform operation on each element in NodeList, output results to a new Array
Array.prototype.map.call(nodeList, function(item) {
return item;
}).forEach(doSomething);
// Filter NodeList, output result to a new Array
Array.prototype.filter.call(nodeList, function(item) {
return /* condition */;
}).forEach(doSomething);
There are many other ways that you can iterate a NodeList that don't require the use of Array methods, here are some more examples:
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(let i = 0; i < nodeList.length; ++i) doSomething(nodeList[i]);
Using a backwards loop (where possible) can save reduce execution time due to a lack of conditional evaluation. In fact, some IDE's will convert the previous loop to the following structure by default.
for(let i = nodeList.length; 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.
let i = 0, node;
while((node = nodeList.item(i++))) doSomething(node);
You can do the same thing with a for loop in the conditional:
let node;
for(let 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]);
You can use a for...of
loop (ECMAScript 2015+) 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);
If you apply the Array Iterator to the prototype of the NodeList class then whenever a new instance of NodeList is created, it will always be iterable. However, it is not advisable to extended native prototypes.
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
for(node of nodeList) doSomething(node);