I've answered a question here before about How to get number of response of JSON? and I suggested for them to use the map
function instead of using a for
loop but someone mented that .map
is not for looping and to use forEach
instead.
Are there any downsides to using map
over a for
loop?
I also researched this and found a site stating that map
> forEach
.
I've answered a question here before about How to get number of response of JSON? and I suggested for them to use the map
function instead of using a for
loop but someone mented that .map
is not for looping and to use forEach
instead.
Are there any downsides to using map
over a for
loop?
I also researched this and found a site stating that map
> forEach
.
-
5
.map
is for iterating arrays and changing each element. – Derek 朕會功夫 Commented Feb 6, 2016 at 5:34 - Possible duplicate of what use does the javascript forEach method have (that map can't do)? – choz Commented Feb 6, 2016 at 5:36
- I find that blog post puzzling. He links to an answer saying map() is probably worse in performance and memory use since it returns an array, and still advocates that because it's cool. Also semantically forEach says what you're doing. Using map to loop isn't clear. – Sami Kuhmonen Commented Feb 6, 2016 at 5:37
-
1
@Derek朕會功夫 How about
foreach
is for iterating arrays and changing each element? Seriously, why would you change each element with.map
since map does not mutate the array on which it is called. – choz Commented Feb 6, 2016 at 5:39 -
1
@choz The reasoning behind the naming of
.map
is because it "maps" values in an array (domain) through a function to a new array (image) in a mathematical sense..forEach
is mainly for general looping over arrays purposes. For example, if you have an array which you want to map it to x+2, you would use.map
instead of.forEach
. – Derek 朕會功夫 Commented Feb 6, 2016 at 5:45
5 Answers
Reset to default 4Map is used to transform each element in an array into another representation, and returns the results in a new sequence. However, since the function is invoked for each item, it is possible that you could make arbitrary calls and return nothing, thus making it act like forEach
, although strictly speaking they are not the same.
Proper use of map (transforming an array of values to another representation):
var source = ["hello", "world"];
var result = source.map(function(value) {
return value.toUpperCase();
});
console.log(result); // should emit ["HELLO, "WORLD"]
Accidentally using .map
to iterate (a semantic error):
var source = ["hello", "world"];
// emits:
// "hello"
// "world"
source.map(function(value) {
console.log(value);
});
The second example is technically valid, it'll pile and it'll run, but that is not the intended use of map
.
"Who cares, if it does what I want?" might be your next question. First of all, map
ignores items at an index that have an assigned value. Also, because map
has an expectation to return a result, it is doing extra things, thus allocating more memory and processing time (although very minute), to a simple process. More importantly, it may confuse yourself or other developers maintaining your code.
The map() method creates a new array with the results of calling a provided function on every element in this array.
https://developer.mozilla/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
map
calls a provided callback function once for each element in an array, in order, and constructs a new array from the results. callback is invoked only for indexes of the array which have assigned values, including undefined. It is not called for missing elements of the array (that is, indexes that have never been set, which have been deleted or which have never been assigned a value).
callback is invoked with three arguments: the value of the element, the index of the element, and the Array object being traversed.
If a thisArg
parameter is provided to map, 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.
map does not mutate the array on which it is called (although callback, if invoked, may do so).
The range of elements processed by map is set before the first invocation of callback. Elements which are appended to the array after the call to map begins will not be visited by callback. If existing elements of the array are changed, or deleted, their value as passed to callback will be the value at the time map visits them; elements that are deleted are not visited.
Ref from MDN:
// Production steps of ECMA-262, Edition 5, 15.4.4.19
// Reference: http://es5.github.io/#x15.4.4.19
if (!Array.prototype.map) {
Array.prototype.map = function(callback, thisArg) {
var T, A, k;
if (this == null) {
throw new TypeError(' this is null or not defined');
}
// 1. Let O be the result of calling ToObject passing the |this|
// value as the argument.
var O = Object(this);
// 2. Let lenValue be the result of calling the Get internal
// method of O with the argument "length".
// 3. Let len be ToUint32(lenValue).
var len = O.length >>> 0;
// 4. If IsCallable(callback) is false, throw a TypeError exception.
// See: http://es5.github./#x9.11
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
// 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 1) {
T = thisArg;
}
// 6. Let A be a new array created as if by the expression new Array(len)
// where Array is the standard built-in constructor with that name and
// len is the value of len.
A = new Array(len);
// 7. Let k be 0
k = 0;
// 8. Repeat, while k < len
while (k < len) {
var kValue, mappedValue;
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the HasProperty internal
// method of O with argument Pk.
// This step can be bined with c
// c. If kPresent is true, then
if (k in O) {
// i. Let kValue be the result of calling the Get internal
// method of O with argument Pk.
kValue = O[k];
// ii. Let mappedValue be the result of calling the Call internal
// method of callback with T as the this value and argument
// list containing kValue, k, and O.
mappedValue = callback.call(T, kValue, k, O);
// iii. Call the DefineOwnProperty internal method of A with arguments
// Pk, Property Descriptor
// { Value: mappedValue,
// Writable: true,
// Enumerable: true,
// Configurable: true },
// and false.
// In browsers that support Object.defineProperty, use the following:
// Object.defineProperty(A, k, {
// value: mappedValue,
// writable: true,
// enumerable: true,
// configurable: true
// });
// For best browser support, use the following:
A[k] = mappedValue;
}
// d. Increase k by 1.
k++;
}
// 9. return A
return A;
};
}
The best way to think about map
is to think of it as a "functional" for loop with some superpowers.
When you call .map
on an array, two things happen.
- You give it a function, and that function gets called every time it iterates. It passes the item into your function at the current index of the loop. Whatever value you return in this function "updates" that given item in a new array.
- The
.map
function returns you an array of all the values that got returned by the function you give to map.
Let's look at an example.
var collection = [1, 2, 3, 4];
var collectionTimesTwo = collection.map(function (item) {
return item * 2;
});
console.log(collection) // 1, 2, 3, 4
console.log(collectionPlusOne) // 2, 4, 6, 8
On the first line we define our original collection, 1 through 4.
On the next couple line we do our map
. This is going to loop over every item in the collection
, and pass each item
to the function. The function returns the item
multiplied by 2
. This ends up generating a new array, collectionTimesTwo
-- the result of multiplying each item in the array by two.
Let's look at one more example, say we have a collection of words and we want to capitalize each one with map
var words = ['hello', 'world', 'foo', 'bar'];
var capitalizedWords = words.map(function (word) {
return word.toUpperCase();
})
console.log(words) // 'hello', 'world', 'foo', 'bar'
console.log(capitalizedWords) // 'HELLO', 'WORLD', 'FOO', 'BAR'
See where we're going?
This lets us work more functionally, rather than like the following
var words = ['hello', 'world', 'foo', 'bar'];
var capitalizedWords = [];
for (var i = 0; i < words.length; i++) {
capitalizedWords[i] = words[i].toUpperCase();
}
There are a few things that can be said objectively, disregarding the subjective parts.
What is clear and concise use? If I use
map()
anyone reading the code assumes I'm doing what it says: mapping the values somehow. Being it a lookup table, calculation or whatever. I take the values and return (the same amount of) values transformed.When I do
forEach()
it is understood I will use all the values as input to do something but I'm not doing any transformations and not returning anything.Chaining is just a side effect, not a reason to use one over the other. How often do your loops return something you can or want to reuse in a loop, unless you're mapping?
Performance. Yes, it might be micro-optimization, but why use a function that causes an array to be gathered and returned if you're not going to use it?
The blog post you linked to is quite messy. It talks about for
using more memory and then remends map()
because it's cool, even though it uses more memory and is worse in performance.
Also as an anecdote the test linked to there runs for
faster than forEach
on my one browser. So objective performance cannot be stated.
Even if opinions shouldn't count on SO, I believe this to be the general opinion: use methods and functions that were made for that use. Meaning for
or forEach()
for looping and map()
for mapping.
The phrase "map isn't for looping" was probably a little bit inaccurate, since of course map
replaces for
-loops.
What the menter was saying was that you should use forEach
when you just want to loop, and use map
when you want to collect results by applying an operating to each of the array elements. Here is a simple example:
> a = [10, 20, 30, 40, 50]
[ 10, 20, 30, 40, 50 ]
> a.map(x => x * 2)
[ 20, 40, 60, 80, 100 ]
> count = 0;
0
> a.forEach(x => count++)
undefined
> count
5
Here map
retains the result of applying a function to each element. You map
when you care about each of the individual results of the operation. In contrast, in your case of counting the number of elements in an array, we don't need to produce a new array. We care only about a single result!
So if you just want to loop, use forEach
. When you need to collect all of your results, use map
.