I was looking at some snippets of code, and I found multiple elements calling a function over a node list with a forEach applied to an empty array.
For example I have something like:
[].forEach.call( document.querySelectorAll('a'), function(el) {
// whatever with the current node
});
but I can't understand how it works. Can anyone explain me the behaviour of the empty array in front of the forEach and how the call works?
[] 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.
The querySelectorAll method returns a NodeList, which is similar to an array, but it's not quite an array. Therefore, it doesn't have a forEach method (which array objects inherit via Array.prototype).
Since a NodeList is similar to an array, array methods will actually work on it, so by using [].forEach.call you are invoking the Array.prototype.forEach method in the context of the NodeList, as if you had been able to simply do yourNodeList.forEach(/*...*/).
Note that the empty array literal is just a shortcut to the expanded version, which you will probably see quite often too:
Array.prototype.forEach.call(/*...*/);
The other answers have explained this code very well, so I'll just add a suggestion.
This is a good example of code that should be refactored for simplicity and clarity. Instead of using [].forEach.call() or Array.prototype.forEach.call() every time you do this, make a simple function out of it:
function forEach( list, callback ) {
Array.prototype.forEach.call( list, callback );
}
Now you can call this function instead of the more complicated and obscure code:
forEach( document.querySelectorAll('a'), function( el ) {
// whatever with the current node
});
It can be better written using
Array.prototype.forEach.call( document.querySelectorAll('a'), function(el) {
});
What is does is document.querySelectorAll('a') returns an object similar to an array, but it does not inherit from the Array type.
So we calls the forEach method from the Array.prototype object with the context as the value returned by document.querySelectorAll('a')
[].forEach.call( document.querySelectorAll('a'), function(el) {
// whatever with the current node
});
It is basically the same as:
var arr = document.querySelectorAll('a');
arr.forEach(function(el) {
// whatever with the current node
});
Want to update on this old question:
The reason to use [].foreach.call() to loop through elements in the modern browsers is mostly over. We can use document.querySelectorAll("a").foreach() directly.
NodeList objects are collections of nodes, usually returned by
properties such as Node.childNodes and methods such as
document.querySelectorAll().
Although NodeList is not an Array, it is possible to iterate over it
with forEach(). It can also be converted to a real Array using
Array.from().
However, some older browsers have not implemented NodeList.forEach()
nor Array.from(). This can be circumvented by using
Array.prototype.forEach() — see this document's Example.
Lots of good info on this page (see answer+answer+comment), but I recently had the same question as the OP, and it took some digging to get the whole picture. So, here's a short version:
The goal is to use Array methods on an array-like NodeList that doesn't have those methods itself.
An older pattern co-opted Array's methods via Function.call(), and used an array literal ([]) rather than than Array.prototype because it was shorter to type:
[].forEach.call(document.querySelectorAll('a'), a => {})
A newer pattern (post ECMAScript 2015) is to use Array.from():
Array.from(document.querySelectorAll('a')).forEach(a => {})
An empty array has a property forEach in its prototype which is a Function object. (The empty array is just an easy way to obtain a reference to the forEach function that all Array objects have.) Function objects, in turn, have a call property which is also a function. When you invoke a Function's call function, it runs the function with the given arguments. The first argument becomes this in the called function.
You can find documentation for the call function here. Documentation for forEach is here.
Just add one line:
NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.forEach;
And voila!
document.querySelectorAll('a').forEach(function(el) {
// whatever with the current node
});
Enjoy :—)
Warning: NodeList is a global class. Don't use this recomendation if you writing public library. However it's very convenient way for increasing self-efficacy when you work on website or node.js app.
Just a quick and dirty solution I always end up using. I wouldn't touch prototypes, just as good practice. Of course, there are a lot of ways to make this better, but you get the idea.
const forEach = (array, callback) => {
if (!array || !array.length || !callback) return
for (var i = 0; i < array.length; i++) {
callback(array[i], i);
}
}
forEach(document.querySelectorAll('.a-class'), (item, index) => {
console.log(`Item: ${item}, index: ${index}`);
});
[] always returns a new array, it is equivalent to new Array() but is guaranteed to return an array because Array could be overwritten by the user whereas [] can not. So this is a safe way to get the prototype of Array, then as described, call is used to execute the function on the arraylike nodelist (this).
Calls a function with a given this value and arguments provided
individually. mdn
Norguard explained WHAT [].forEach.call() does and James Allardice WHY we do it: because querySelectorAll returns a NodeList that doesn't have a forEach method...
Unless you have modern browser like Chrome 51+, Firefox 50+, Opera 38, Safari 10.
If not you can add a Polyfill:
if (window.NodeList && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = function (callback, thisArg) {
thisArg = thisArg || window;
for (var i = 0; i < this.length; i++) {
callback.call(thisArg, this[i], i, this);
}
};
}
let's say you have : const myList= document.querySelectorAll("p");
This will return an list/array of all in your HTML.
Now Array.prototype.forEach.call(myList, myCallback)
is equivalent to [].forEach.call(myList, myCallback)
where 'myCallback' is a callback function.
You are basically running the callback function on each element of myList.
Hope this helped you!
I don't know if there is any restriction, but it works.
I turned the nodeList into an iterator object using the spread operator and mapped it:
let _btns = document.querySelectorAll('.btn');
[..._btns].map(function(elem, i) {
elem.addEventListener('click', function (e) {
console.log(elem.textContent);
})
})
.btn {
padding: 5px;
color:#fff;
background-color: darkred;
text-align:center;
color: white;
}
<button class="btn">button 1</button>
<button class="btn">button 2</button>
Related
I was looking at some snippets of code, and I found multiple elements calling a function over a node list with a forEach applied to an empty array.
For example I have something like:
[].forEach.call( document.querySelectorAll('a'), function(el) {
// whatever with the current node
});
but I can't understand how it works. Can anyone explain me the behaviour of the empty array in front of the forEach and how the call works?
[] 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.
The querySelectorAll method returns a NodeList, which is similar to an array, but it's not quite an array. Therefore, it doesn't have a forEach method (which array objects inherit via Array.prototype).
Since a NodeList is similar to an array, array methods will actually work on it, so by using [].forEach.call you are invoking the Array.prototype.forEach method in the context of the NodeList, as if you had been able to simply do yourNodeList.forEach(/*...*/).
Note that the empty array literal is just a shortcut to the expanded version, which you will probably see quite often too:
Array.prototype.forEach.call(/*...*/);
The other answers have explained this code very well, so I'll just add a suggestion.
This is a good example of code that should be refactored for simplicity and clarity. Instead of using [].forEach.call() or Array.prototype.forEach.call() every time you do this, make a simple function out of it:
function forEach( list, callback ) {
Array.prototype.forEach.call( list, callback );
}
Now you can call this function instead of the more complicated and obscure code:
forEach( document.querySelectorAll('a'), function( el ) {
// whatever with the current node
});
It can be better written using
Array.prototype.forEach.call( document.querySelectorAll('a'), function(el) {
});
What is does is document.querySelectorAll('a') returns an object similar to an array, but it does not inherit from the Array type.
So we calls the forEach method from the Array.prototype object with the context as the value returned by document.querySelectorAll('a')
[].forEach.call( document.querySelectorAll('a'), function(el) {
// whatever with the current node
});
It is basically the same as:
var arr = document.querySelectorAll('a');
arr.forEach(function(el) {
// whatever with the current node
});
Want to update on this old question:
The reason to use [].foreach.call() to loop through elements in the modern browsers is mostly over. We can use document.querySelectorAll("a").foreach() directly.
NodeList objects are collections of nodes, usually returned by
properties such as Node.childNodes and methods such as
document.querySelectorAll().
Although NodeList is not an Array, it is possible to iterate over it
with forEach(). It can also be converted to a real Array using
Array.from().
However, some older browsers have not implemented NodeList.forEach()
nor Array.from(). This can be circumvented by using
Array.prototype.forEach() — see this document's Example.
Lots of good info on this page (see answer+answer+comment), but I recently had the same question as the OP, and it took some digging to get the whole picture. So, here's a short version:
The goal is to use Array methods on an array-like NodeList that doesn't have those methods itself.
An older pattern co-opted Array's methods via Function.call(), and used an array literal ([]) rather than than Array.prototype because it was shorter to type:
[].forEach.call(document.querySelectorAll('a'), a => {})
A newer pattern (post ECMAScript 2015) is to use Array.from():
Array.from(document.querySelectorAll('a')).forEach(a => {})
An empty array has a property forEach in its prototype which is a Function object. (The empty array is just an easy way to obtain a reference to the forEach function that all Array objects have.) Function objects, in turn, have a call property which is also a function. When you invoke a Function's call function, it runs the function with the given arguments. The first argument becomes this in the called function.
You can find documentation for the call function here. Documentation for forEach is here.
Just add one line:
NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.forEach;
And voila!
document.querySelectorAll('a').forEach(function(el) {
// whatever with the current node
});
Enjoy :—)
Warning: NodeList is a global class. Don't use this recomendation if you writing public library. However it's very convenient way for increasing self-efficacy when you work on website or node.js app.
Just a quick and dirty solution I always end up using. I wouldn't touch prototypes, just as good practice. Of course, there are a lot of ways to make this better, but you get the idea.
const forEach = (array, callback) => {
if (!array || !array.length || !callback) return
for (var i = 0; i < array.length; i++) {
callback(array[i], i);
}
}
forEach(document.querySelectorAll('.a-class'), (item, index) => {
console.log(`Item: ${item}, index: ${index}`);
});
[] always returns a new array, it is equivalent to new Array() but is guaranteed to return an array because Array could be overwritten by the user whereas [] can not. So this is a safe way to get the prototype of Array, then as described, call is used to execute the function on the arraylike nodelist (this).
Calls a function with a given this value and arguments provided
individually. mdn
Norguard explained WHAT [].forEach.call() does and James Allardice WHY we do it: because querySelectorAll returns a NodeList that doesn't have a forEach method...
Unless you have modern browser like Chrome 51+, Firefox 50+, Opera 38, Safari 10.
If not you can add a Polyfill:
if (window.NodeList && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = function (callback, thisArg) {
thisArg = thisArg || window;
for (var i = 0; i < this.length; i++) {
callback.call(thisArg, this[i], i, this);
}
};
}
let's say you have : const myList= document.querySelectorAll("p");
This will return an list/array of all in your HTML.
Now Array.prototype.forEach.call(myList, myCallback)
is equivalent to [].forEach.call(myList, myCallback)
where 'myCallback' is a callback function.
You are basically running the callback function on each element of myList.
Hope this helped you!
I don't know if there is any restriction, but it works.
I turned the nodeList into an iterator object using the spread operator and mapped it:
let _btns = document.querySelectorAll('.btn');
[..._btns].map(function(elem, i) {
elem.addEventListener('click', function (e) {
console.log(elem.textContent);
})
})
.btn {
padding: 5px;
color:#fff;
background-color: darkred;
text-align:center;
color: white;
}
<button class="btn">button 1</button>
<button class="btn">button 2</button>
I want to modify each object in an array, as well as remove duplicates. I am already using lodash's uniqWith to compare items so I figured I would do some other logic within the comparator function to modify each item so I can avoid setting up another loop. Are there any problems with having extra logic within the comparator like below?
import uniqWith from 'lodash/uniqWith';
const transformedArray = uniqWith(
arrayOfObjects,
(currObject, otherObject): boolean => {
// modifying current object's properties, is this legit??
if (<someCondition>) {
currObject.someProperty = true;
}
// actual comparison logic
if (currObject.uuid === otherObject.uuid) {
return true;
}
return false;
},
);
The uniqWith documentation only says the following about the callback invocation:
The comparator is invoked with two arguments: (arrVal, othVal).
I personally wouldn't use the uniqWith compare function, for iteration work. The documentation doesn't disclose how often or in what order the compare function will be called. Say you have the array [1,2,3]. I would assume each element is compared against all elements in the output, to check for uniqueness. The implementation might look something like:
function uniqWith(array, compareFn) {
const unique = [];
for (const current of array) {
const isDuplicate = unique.some(other => compareFn(current, other));
if (!isDuplicate) unique.push(current);
}
return unique;
}
If this is indeed the case that means the callback is never called for 1, once for 2, and twice for 3.
Generally speaking don't use callback functions to do iteration work, unless the function that accepts the callback discloses when/how often/what order etc. this function is called.
Take for example the Array forEach method. The documentation discloses all these things.
forEach() calls a provided callback function once for each element in an array in ascending order. It is not invoked for index properties that have been deleted or are uninitialized. (For sparse arrays, see example below.)
Short answer: no, if by "wrong" you mean a breaking issue.
Medium Answer: It shouldn't interfere with the method. Just be aware that doing mutations in unique locations, where you otherwise may not expect them, is trading clarity for brevity- which can lead to confusion and debugging nightmares later down the road if you lose track of them.
After more thorough testing I realized that there is some optimization done in lodash/uniqWith where not every item is necessarily passed to the comparator function as the currObject. So as far as my example code in the original question, it is not guaranteed that every item in array will receive the .someProperty = true. Should've tested this more before posting, but still appreciate the feedback.
I was looking at some snippets of code, and I found multiple elements calling a function over a node list with a forEach applied to an empty array.
For example I have something like:
[].forEach.call( document.querySelectorAll('a'), function(el) {
// whatever with the current node
});
but I can't understand how it works. Can anyone explain me the behaviour of the empty array in front of the forEach and how the call works?
[] 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.
The querySelectorAll method returns a NodeList, which is similar to an array, but it's not quite an array. Therefore, it doesn't have a forEach method (which array objects inherit via Array.prototype).
Since a NodeList is similar to an array, array methods will actually work on it, so by using [].forEach.call you are invoking the Array.prototype.forEach method in the context of the NodeList, as if you had been able to simply do yourNodeList.forEach(/*...*/).
Note that the empty array literal is just a shortcut to the expanded version, which you will probably see quite often too:
Array.prototype.forEach.call(/*...*/);
The other answers have explained this code very well, so I'll just add a suggestion.
This is a good example of code that should be refactored for simplicity and clarity. Instead of using [].forEach.call() or Array.prototype.forEach.call() every time you do this, make a simple function out of it:
function forEach( list, callback ) {
Array.prototype.forEach.call( list, callback );
}
Now you can call this function instead of the more complicated and obscure code:
forEach( document.querySelectorAll('a'), function( el ) {
// whatever with the current node
});
It can be better written using
Array.prototype.forEach.call( document.querySelectorAll('a'), function(el) {
});
What is does is document.querySelectorAll('a') returns an object similar to an array, but it does not inherit from the Array type.
So we calls the forEach method from the Array.prototype object with the context as the value returned by document.querySelectorAll('a')
[].forEach.call( document.querySelectorAll('a'), function(el) {
// whatever with the current node
});
It is basically the same as:
var arr = document.querySelectorAll('a');
arr.forEach(function(el) {
// whatever with the current node
});
Want to update on this old question:
The reason to use [].foreach.call() to loop through elements in the modern browsers is mostly over. We can use document.querySelectorAll("a").foreach() directly.
NodeList objects are collections of nodes, usually returned by
properties such as Node.childNodes and methods such as
document.querySelectorAll().
Although NodeList is not an Array, it is possible to iterate over it
with forEach(). It can also be converted to a real Array using
Array.from().
However, some older browsers have not implemented NodeList.forEach()
nor Array.from(). This can be circumvented by using
Array.prototype.forEach() — see this document's Example.
Lots of good info on this page (see answer+answer+comment), but I recently had the same question as the OP, and it took some digging to get the whole picture. So, here's a short version:
The goal is to use Array methods on an array-like NodeList that doesn't have those methods itself.
An older pattern co-opted Array's methods via Function.call(), and used an array literal ([]) rather than than Array.prototype because it was shorter to type:
[].forEach.call(document.querySelectorAll('a'), a => {})
A newer pattern (post ECMAScript 2015) is to use Array.from():
Array.from(document.querySelectorAll('a')).forEach(a => {})
An empty array has a property forEach in its prototype which is a Function object. (The empty array is just an easy way to obtain a reference to the forEach function that all Array objects have.) Function objects, in turn, have a call property which is also a function. When you invoke a Function's call function, it runs the function with the given arguments. The first argument becomes this in the called function.
You can find documentation for the call function here. Documentation for forEach is here.
Just add one line:
NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.forEach;
And voila!
document.querySelectorAll('a').forEach(function(el) {
// whatever with the current node
});
Enjoy :—)
Warning: NodeList is a global class. Don't use this recomendation if you writing public library. However it's very convenient way for increasing self-efficacy when you work on website or node.js app.
Just a quick and dirty solution I always end up using. I wouldn't touch prototypes, just as good practice. Of course, there are a lot of ways to make this better, but you get the idea.
const forEach = (array, callback) => {
if (!array || !array.length || !callback) return
for (var i = 0; i < array.length; i++) {
callback(array[i], i);
}
}
forEach(document.querySelectorAll('.a-class'), (item, index) => {
console.log(`Item: ${item}, index: ${index}`);
});
[] always returns a new array, it is equivalent to new Array() but is guaranteed to return an array because Array could be overwritten by the user whereas [] can not. So this is a safe way to get the prototype of Array, then as described, call is used to execute the function on the arraylike nodelist (this).
Calls a function with a given this value and arguments provided
individually. mdn
Norguard explained WHAT [].forEach.call() does and James Allardice WHY we do it: because querySelectorAll returns a NodeList that doesn't have a forEach method...
Unless you have modern browser like Chrome 51+, Firefox 50+, Opera 38, Safari 10.
If not you can add a Polyfill:
if (window.NodeList && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = function (callback, thisArg) {
thisArg = thisArg || window;
for (var i = 0; i < this.length; i++) {
callback.call(thisArg, this[i], i, this);
}
};
}
let's say you have : const myList= document.querySelectorAll("p");
This will return an list/array of all in your HTML.
Now Array.prototype.forEach.call(myList, myCallback)
is equivalent to [].forEach.call(myList, myCallback)
where 'myCallback' is a callback function.
You are basically running the callback function on each element of myList.
Hope this helped you!
I don't know if there is any restriction, but it works.
I turned the nodeList into an iterator object using the spread operator and mapped it:
let _btns = document.querySelectorAll('.btn');
[..._btns].map(function(elem, i) {
elem.addEventListener('click', function (e) {
console.log(elem.textContent);
})
})
.btn {
padding: 5px;
color:#fff;
background-color: darkred;
text-align:center;
color: white;
}
<button class="btn">button 1</button>
<button class="btn">button 2</button>
I was working on a short script to change <abbr> elements' inner text, but found that nodelist does not have a forEach method. I know that nodelist doesn't inherit from Array, but doesn't it seem like forEach would be a useful method to have? Is there a particular implementation issue I am not aware of that prevents adding forEach to nodelist?
Note: I am aware that Dojo and jQuery both have forEach in some form for their nodelists. I cannot use either due to limitations.
NodeList now has forEach() in all major browsers
See nodeList forEach() on MDN.
Original answer
None of these answers explain why NodeList doesn't inherit from Array, thus allowing it to have forEach and all the rest.
The answer is found on this es-discuss thread. In short, it breaks the web:
The problem was code that incorrectly assumed instanceof to mean that the instance was an Array in combination with Array.prototype.concat.
There was a bug in Google's Closure Library which caused almost all Google's apps to fail due to this. The library was updated as soon as this was found but there might still be code out there that makes the same incorrect assumption in combination with concat.
That is, some code did something like
if (x instanceof Array) {
otherArray.concat(x);
} else {
doSomethingElseWith(x);
}
However, concat will treat "real" arrays (not instanceof Array) differently from other objects:
[1, 2, 3].concat([4, 5, 6]) // [1, 2, 3, 4, 5, 6]
[1, 2, 3].concat(4) // [1, 2, 3, 4]
so that means that the above code broke when x was a NodeList, because before it went down the doSomethingElseWith(x) path, whereas afterward it went down the otherArray.concat(x) path, which did something weird since x wasn't a real array.
For some time there was a proposal for an Elements class that was a real subclass of Array, and would be used as "the new NodeList". However, that was removed from the DOM Standard, at least for now, since it wasn't feasible to implement yet for a variety of technical and specification-related reasons.
You can do
Array.prototype.forEach.call (nodeList, function (node) {
// Your code here.
} );
You can consider creating a new array of nodes.
var nodeList = document.getElementsByTagName('div'),
nodes = Array.prototype.slice.call(nodeList,0);
// nodes is an array now.
nodes.forEach(function(node){
// do your stuff here.
});
Note: This is just a list/array of node references we are creating here, no duplicate nodes.
nodes[0] === nodeList[0] // will be true
In short, its a design conflict to implement that method.
From MDN:
Why can't I use forEach or map on a NodeList?
NodeList are used very much like arrays and it would be tempting to
use Array.prototype methods on them. This is, however, impossible.
JavaScript has an inheritance mechanism based on prototypes. Array
instances inherit array methods (such as forEach or map) because their
prototype chain looks like the following:
myArray --> Array.prototype --> Object.prototype --> null (the
prototype chain of an object can be obtained by calling several times
Object.getPrototypeOf)
forEach, map and the likes are own properties of the Array.prototype
object.
Unlike arrays, NodeList prototype chain looks like the following:
myNodeList --> NodeList.prototype --> Object.prototype --> null
NodeList.prototype contains the item method, but none of the
Array.prototype methods, so they cannot be used on NodeLists.
Source: https://developer.mozilla.org/en-US/docs/DOM/NodeList (scroll down to Why can't I use forEach or map on a NodeList?)
Never say never, it's 2016 and the NodeList object has implemented a forEach method in latest chrome (v52.0.2743.116).
It's too early to use it in production as other browser don't support this yet (tested FF 49) but I would guess that this will be standardized soon.
If you would like using forEach on NodeList, just copy that function from Array:
NodeList.prototype.forEach = Array.prototype.forEach;
Thats all, now you can use it at the same manner you would for Array:
document.querySelectorAll('td').forEach(function(o){
o.innerHTML = 'text';
});
In ES2015, you can now use forEach method to the nodeList.
document.querySelectorAll('abbr').forEach( el => console.log(el));
See The MDN Link
However if you want to use HTML Collections or other array-like objects, in es2015, you can use Array.from() method. This method takes an array-like or iterable object (including nodeList, HTML Collections, strings etc) and returns a new Array instance. You can use it like this:
const elements = document.getElementsByTagName('abbr');
Array.from(elements).forEach( el => console.log(el));
As Array.from() method is shimmable, you can use it in es5 code like this
var elements = document.getElementsByTagName('abbr');
Array.from(elements).forEach( function(el) {
console.log(el);
});
For details, see the MDN page.
To check current browser support.
OR
another es2015 way is to use spread operator.
[...document.querySelectorAll('abbr')].forEach( el => console.log(el));
MDN spread operator
Spread Operator - Browser Support
My solution:
//foreach for nodeList
NodeList.prototype.forEach = Array.prototype.forEach;
//foreach for HTML collection(getElementsByClassName etc.)
HTMLCollection.prototype.forEach = Array.prototype.forEach;
NodeList is part of the DOM API. Look at the ECMAScript bindings which apply to JavaScript as well. http://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html. The nodeList and a read-only length property and item(index) function to return a node.
The answer is, you have to iterate. There is no alternative. Foreach will not work.
I work with Java DOM API bindings and have the same problem.
Check MDN for NodeList.forEach specification.
NodeList.forEach(function(item, index, nodeList) {
// code block here
});
In IE, use akuhn's answer:
[].forEach.call(NodeList, function(item, index, array) {
// code block here
});
You can add forEach polyfill for old browsers just one line of code:
window.NodeList && !NodeList.prototype.forEach && (NodeList.prototype.forEach = Array.prototype.forEach);
https://udn.realityripple.com/docs/Web/API/NodeList/forEach
As far as I know there are three ways of finding out if an object is an Array
by isArray function if implemented
Array.isArray()
by toString
Object.prototype.toString.apply( obj ) === "[object Array]"
and by instanceof
obj instanceof Array
Is there any reason to choose one over the other?
The best way is probably to use the standard Array.isArray(), if it's implemented by the engine:
isArray = Array.isArray(myObject)
MDN recommends to use the toString() method when Array.isArray isn't implemented:
Compatibility
Running the following code before any other code will create
Array.isArray if it's not natively available. This relies on
Object.prototype.toString being unchanged and call resolving to the
native Function.prototype.call method.
if(!Array.isArray) {
Array.isArray = function (arg) {
return Object.prototype.toString.call(arg) == '[object Array]';
};
}
Both jQuery and underscore.js[source] take the toString() === "[object Array]" way.
Unless it was proven that the former has significant performance benefits and my app required every last ounce of speed I would go for the latter.
The reason is readability, pure and simple.
instanceof tests whether the given constructor (Array) is in the object's prototype chain, while your second approach only checks the actual type of the object. In other words, if your object inherits from Array, the second test will be true, but the first will be false. Now, it's not typically done to inherit from Array (it doesn't work right in IE), but walking the prototype chain presumably adds some overhead (especially if the object isn't an array).
If what you're trying to do is to decide whether a parameter passed to you is an array that you should iterate over, there's a fair amount of code out there that just looks for a .length attribute and treats as an array or a pseudo-array if that attribute is present.
This is because there are lots of things that aren't actually arrays (but are pseudo arrays with array like capabilities) that you may want your code to treat like an array. Examples of some of these kinds of things are a jQuery object or a nodeList returned from many DOM calls. Here's a code example:
// accepts:
// single DOM element
// array of DOM elements
// nodeList as returned from various DOM functions like getElementsByClassName
// any array like object with a .length attribute and items in numeric indexes from 0 to .length-1 like a jQuery object
function hideElements(input) {
if (input.length !== undefined) {
for (var i = 0, len = input.length; i < len; i++) {
input[i].style.display = "none";
}
} else {
input.style.display = "none";
}
return(input);
}
The jQuery .each() function also just tests the parameter passed to it for .length (and verifying that it's not a function) before deciding it's something it should iterate like an array.
If that isn't the problem you're trying to solve, I can find two references to using the first technique:
jQuery's implementation of isArray uses the first technique.
MDN (Mozilla Developer Network) recommends the first one here.