Loop through NodeList: Array.prototype.forEach.call() vs Array.from().forEach
Asked Answered
A

3

7

So I want an easy way to loop through nodelists, and I always hated that I can't use forEach on nodeLists.

So, now I do: Array.prototype.forEach.call(nodeList, callback).

and for index, i do: Array.prototype.indexOf.call(nodeList, node).

and for pretty much everything I use Array.prototype on nodeLists.

But I'm wondering if these are considered hacks?

Are they the right way to do it?

Also, assuming I don't actually need an array from nodeList, is there an advantage of using Array.from(nodeList).forEach(callback) instead?

Alar answered 17/4, 2017 at 10:47 Comment(0)
T
4

Array.prototype.forEach.call(nodeList, callback) will apply the logic of forEach on the node list. forEach just have a for loop in it that goes from index 0 to this.length and calling a callback on each of the items. This method is calling forEach passing the node list as its this value, since node lists have similar properties of an array (length and 0, 1, ...), everything works fine.

Array.from(nodeList).forEach(callback) will create a new array from the node list, then use forEach on that new array. This second method could be split into two self explanatory lines:

var newArray = Array.from(nodeList);  // create a new array out of the node list
newArray.forEach(callback);           // call forEach on the new array

The first approach is better because it doesn't create additional uneeded resources and it work on node lists directly.

Tautology answered 17/4, 2017 at 10:49 Comment(10)
"The first approach is better because it doesn't create additional uneeded resources and it work on node lists directly!" any perf tests to prove this claim?Addle
@YuryTarabanko Function.prototype.call says ...calls a function with a given this value... thus the node list is passed as this itself! And Array.from says ...creates a new Array instance from an array-like or iterable object!Tautology
And? How do you know using dynamic context when calling .forEach doesn't result in some sort of de-optimization? There is nothing "obvious!" when it comes to performance. I mean your claim that "the first approach is better" not the underlying machinery.Addle
@YuryTarabanko Well one can easely observe that the first method is only looping once! The second however is looping twice (one loop for creating a new array out of the node list, the second is when calling forEach on the node list)! So the first method is much more optimized than the second (specially for large node lists or any array-like objects)!Tautology
Can you prove it with test? Using exclamation marks doesn't prove anything.Addle
@YuryTarabanko The time that the second method takes is almost twice the time that the first method is taking: jsfiddle.net/wtz90wb3!!!!!!!!!!!!!!!!!!!!!Tautology
Your tests were measuring dom operation performance. jsfiddle.net/3L6967vz The results are not that stable and obvious. Keep calm and do perf test before making claims. "VM1007:62 forEach: 14.3ms 2017-04-17 14:41:50.550 VM1007:66 from: 22.4ms 2017-04-17 14:41:50.575 VM1007:70 from with cb: 23.6ms" vs "forEach: 18.5ms 2017-04-17 14:41:54.436 VM1007:66 from: 15.2ms 2017-04-17 14:41:54.450 VM1007:70 from with cb: 14.2ms"Addle
@YuryTarabanko and one thing you should observe, node lists contain objects so the copy using Array.from won't take much time as it doesn't clone the object it's just copy their references. If the array-like object contains basic types such as string which necessitate copying then the gap between the first and the second method quiet big performence-wise! jsfiddle.net/37bt902o!Tautology
And BTW I'm not arguing that the first method might be faster. I am just trying to convince you that such a claims should only be made based on tests. Both operations have the same Big-O. So you can't simply tell which one is faster using only your imagination and exclamation marks.Addle
@YuryTarabanko It's clear as the sun that the first is faster than the second! You just don't see it! Sometimes it is obvious which is optimized without the need for tests.Tautology
H
4

The first method is ES5 compatible:

Array.prototype.forEach.call(nodeList, callback).

The second method uses Array.from which was only defined in ES6:

Array.from(nodeList).forEach(callback)

However, you are not optimising the use of Array.from here, because you first create the whole array, and then iterate over it with forEach.

Instead use the second argument of Array.from:

Array.from(nodeList, callback)

Now the whole operation happens in one iteration.

The nice thing is that in the above expression, the callback is used as a mapping function, so if it returns a value, the whole expression returns the array as defined by those return values. But using this is of course optional. For instance, you could create an array with the text contents of the nodes like this:

texts = Array.from(nodeList, node => node.textContent)

Warning

If you use Array.from with callback, and the node list is a live node list (see NodeList), and you mutate the node list in the callback, then this can have undesired effects. In that case use the two step method where you first turn the node list into an array, and only then perform the callback on its entries.

Humoral answered 17/4, 2017 at 11:9 Comment(0)
Y
2
var array=Array.from(nodeList);
//after some time
var array2=Array.from(nodeList);

If you compare these Arrays youll see that they are not necessarily equal. NodeLists reflect the change of the DOM, as you copy them the arrays stay static. If you want this behaviour / dont care about youre fine. However Array.from is quite new, therefore it isnt understood by older browsers, so it shouldnt be used in productional environments ( if youre not using sth like Babel).

You answered 17/4, 2017 at 10:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.