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:
- the element in the array
- the index of the element (third element would pass
2
) - 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 toforEach()
, it will be passed tocallback
when invoked, for use as itsthis
value. Otherwise, the valueundefined
will be passed for use as itsthis
value. Thethis
value ultimately observable bycallback
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
HTML Anchor Link - Href and Onclick Both
Remove Whitespaces Inside a String in JavaScript
How to Dynamically Scale Text Size Based on Browser Width
Access Event to Call Preventdefault from Custom Function Originating from Onclick Attribute of Tag
JSON Object Undefined in Internet Explorer 8
Switch Statement for Multiple Cases in JavaScript
How to Detect a Textbox's Content Has Changed
Switch Statement for Greater-Than/Less-Than
Difference Between Knockout View Models Declared as Object Literals VS Functions
How to Manually Trigger Validation with Jquery Validate
How to Disable an <Option> in a <Select> Based on Its Value in JavaScript
JavaScript Onclick Event Over Flash Object
Add/Delete Table Rows Dynamically Using JavaScript
How to "Await" for a Callback to Return
When Is the Comma Operator Useful
Date Constructor Returns Nan in Ie, But Works in Firefox and Chrome