melvnberd
melvnberd

Reputation: 3163

is `.map` not for looping?

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 commented 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 .

Upvotes: 0

Views: 2658

Answers (5)

Ray Toal
Ray Toal

Reputation: 88378

The phrase "map isn't for looping" was probably a little bit inaccurate, since of course map replaces for-loops.

What the commenter 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.

Upvotes: 1

moribvndvs
moribvndvs

Reputation: 42497

Map 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 compile 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.

Upvotes: 4

Sami Kuhmonen
Sami Kuhmonen

Reputation: 31143

There are a few things that can be said objectively, disregarding the subjective parts.

  1. 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?

  2. 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 recommends 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.

Upvotes: 1

Atticus
Atticus

Reputation: 6720

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.

  1. 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.
  2. 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();
}

Upvotes: 1

Vicky Gonsalves
Vicky Gonsalves

Reputation: 11707

The map() method creates a new array with the results of calling a provided function on every element in this array.

https://developer.mozilla.org/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.com/#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 combined 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;
  };
}

Upvotes: 2

Related Questions