What Does [].Foreach.Call() Do in JavaScript

What does [].forEach.call() do in JavaScript?

[] is an array.

This array isn't used at all.

It's being put on the page, because using an array gives you access to array prototypes, like .forEach.

This is just faster than typing Array.prototype.forEach.call(...);

Next, forEach is a function which takes a function as an input...

[1,2,3].forEach(function (num) { console.log(num); });

...and for each element in this (where this is array-like, in that it has a length and you can access its parts like this[1]) it will pass three things:

  1. the element in the array
  2. the index of the element (third element would pass 2)
  3. a reference to the array

Lastly, .call is a prototype which functions have (it's a function which gets called on other functions).

.call will take its first argument and replace this inside of the regular function with whatever you passed call, as the first argument (undefined or null will use window in everyday JS, or will be whatever you passed, if in "strict-mode"). The rest of the arguments will be passed to the original function.

[1, 2, 3].forEach.call(["a", "b", "c"], function (item, i, arr) {
console.log(i + ": " + item);
});
// 0: "a"
// 1: "b"
// 2: "c"

Therefore, you're creating a quick way to call the forEach function, and you're changing this from the empty array to a list of all <a> tags, and for each <a> in-order, you are calling the function provided.

EDIT

Logical Conclusion / Cleanup

Below, there's a link to an article suggesting that we scrap attempts at functional programming, and stick to manual, inline looping, every time, because this solution is hack-ish and unsightly.

I'd say that while .forEach is less helpful than its counterparts, .map(transformer), .filter(predicate), .reduce(combiner, initialValue), it still serves purposes when all you really want to do is modify the outside world (not the array), n-times, while having access to either arr[i] or i.

So how do we deal with the disparity, as Motto is clearly a talented and knowledgeable guy, and I would like to imagine that I know what I'm doing/where I'm going (now and then... ...other times it's head-first learning)?

The answer is actually quite simple, and something Uncle Bob and Sir Crockford would both facepalm, due to the oversight:

clean it up.

function toArray (arrLike) { // or asArray(), or array(), or *whatever*
return [].slice.call(arrLike);
}

var checked = toArray(checkboxes).filter(isChecked);
checked.forEach(listValues);

Now, if you're questioning whether you need to do this, yourself, the answer may well be no...

This exact thing is done by... ...every(?) library with higher-order features these days.

If you're using lodash or underscore or even jQuery, they're all going to have a way of taking a set of elements, and performing an action n-times.

If you aren't using such a thing, then by all means, write your own.

lib.array = (arrLike, start, end) => [].slice.call(arrLike, start, end);
lib.extend = function (subject) {
var others = lib.array(arguments, 1);
return others.reduce(appendKeys, subject);
};

Update for ES6(ES2015) and Beyond

Not only is a slice( )/array( )/etc helper method going to make life easier for people who want to use lists just like they use arrays (as they should), but for the people who have the luxury of operating in ES6+ browsers of the relatively-near future, or of "transpiling" in Babel today, you have language features built in, which make this type of thing unnecessary.

function countArgs (...allArgs) {
return allArgs.length;
}

function logArgs (...allArgs) {
return allArgs.forEach(arg => console.log(arg));
}

function extend (subject, ...others) { /* return ... */ }

var nodeArray = [ ...nodeList1, ...nodeList2 ];

Super-clean, and very useful.

Look up the Rest and Spread operators; try them out at the BabelJS site; if your tech stack is in order, use them in production with Babel and a build step.


There's no good reason not to be able to use the transform from non-array into array... ...just don't make a mess of your code doing nothing but pasting that same ugly line, everywhere.

forEach() vs Array.prototype.forEach.call()

"class methods" in JavaScript are actually functions defined on a prototype. That means that even if an object does not inherit from the Array prototype, you can call Array methods on it, as long as it follows the array structure (i.e.: It is an object with a length property and properties indexed by integers). However, the object holds no reference to Array.prototype, so you need to explicitly select Array.prototype as the object the method lives in.

The document.querySelectorAll function returns a NodeList, which is neither an Array nor inherits from the Array prototype. However, as a NodeList has a similar internal structure to an Array, you can still use the forEach function. But since NodeList does not inherit from the Array prototype, trying to use .forEach on a NodeList would raise an error (this is not exactly true - see the note at the end of my answer). For this reason, you need to explicitly state that you are calling a method from Array.prototype on the NodeList, and that is done by using the .call method from Function.prototype.

In summary:

Array.prototype.forEach.call(links, function(link) { /* something */ })

means:

Take the forEach function from Array.prototype and call it on links, which is a non-Array object, with some function as its argument.

Note that on recent versions of browsers, the NodeList prototype does provide a forEach method which works the same as the Array one, so the example from the Electron API is probably using the Array version for compatibility with older versions. If you have a web app and only care about supporting modern versions of Chrome and Firefox, you can just call forEach on your NodeList. In fact, since Electron updates about 2 weeks after whenever Chrome updates, it should be safe to use NodeList.prototype.forEach in Electron. :)

Speed of [].forEach.call(...?

Here's a nice performance comparison. According to it Array.forEach is slower than a native for loop.

array.forEach.call vs array.map.call

forEach operates on the original array elements. (If you want just iterate over all element you should use forEach)

map is running through your array, applying a function to each element, and emitting the result as a new array. (if you want to apply some changes to each element you should use map)

Why calling array.prototype.forEach.call() with an array set to THIS object not working

Because assuming forEach has not been overwritten, and this array still has the normal prototype, this:

Array.prototype.forEach.call(number,function(elem){ });

Is no different from:

number.forEach(function(elem){ });

In a forEach callback function, unless you pass the thisArg, the function is called like a normal callback.

From MDN:

If a thisArg parameter is provided to forEach(), it will be passed to callback when invoked, for use as its this value. Otherwise, the value undefined will be passed for use as its this value. The this value ultimately observable by callback is determined according to the usual rules for determining the this seen by a function.

To set the thisArg while using .call, you would need to pass one more argument:

Array.prototype.forEach.call(number,function(elem){ }, number);

Calling forEach on an object

The problem is that your object does not have a length telling .forEach() how far to iterate.

This is important because JS arrays can be sparse, so it can't simply check for the existence of the property to determine if it should quit, and it would just keep incrementing, assuming that the next index may have a value.

So without the length properly set, the iteration fails on (or before, I don't remember) the first comparison.

In your example, to correctly represent the object as an array-like object, it could look like this:

var arr={0:'a', 1:'b', 2:'c', 3:'d', length: 4}

Now .forEach() will successfully iterate indices 0-3.

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

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.

Alternative for [].forEach.call(...) in ECMA6Script

You wouldn't use forEach at all any more in ES6. You'd use a for of loop:

for (let div of document.querySelectorAll('div'))
div.style.color = 'green';

Apart from that, you can use Array.from to cast an iterable object to an array and then invoke .forEach on that; but in fact with the upcoming DOM spec this is unnecessary where querySelectorAll will return an Elements collection that does inherit from Array in the ES6 way - so you can call the .forEach method directly on it!



Related Topics



Leave a reply



Submit