Thoroughly understand JavaScript forEach & map

background

In JavaScript, we are certainly familiar with array traversal. The most common two are forEach and map.

(of course, there are others such as for, for in, for of, reduce, filter, every, some,...)

The reason why I have to write this a few days ago is that I made a low-level and stupid mistake when I wrote the code a few days ago, and finally made a small bug.

Finally find out the reason, angry, even a little want to laugh, today write these two methods.

text

The code of my street fighting is like this. To add an attribute to an object in an array, I wrote the following code at will:

// Add input_quantity into every list item
const dataSoruceAdapter = data => data.forEach((item) => {
  item.input_quantity = item.quantity
})

// ...
const transformedDataSource = dataSoruceAdapter(defaultDataSource);
const [orderList, setOrderList] = useState(transformedDataSource);

Later, we found that the array is empty

I feel stupid. Change it:

const dataSoruceAdapter = data => data.map((item) => {
  item.input_quantity = item.quantity
  return item
})

Get it done.

Let's take a closer look at forEach and map:

Comparison and conclusion

forEach: performs the provided function for each element.

map: create a new array, each element of which is obtained by calling the function provided by each element in the array.

Come to the conclusion:

The forEach method does not return the execution result, but is undefined.

That is, forEach will modify the original array, and the map method will get a new array and return it.

Let's take a look at specific examples.

forEach

The forEach method performs a callback function in ascending order for each item with valid values in the array, and the deleted or uninitialized items will be skipped (for example, on a sparse array).

forEach receives two parameters: arr.forEach(callback[, thisArg]);

The callback function is passed in three parameters in turn:

  1. Array the value of the current item
  2. Index of array current item
  3. Array object itself

For example:

const arr = ['1', '2', '3'];
// callback function takes 3 parameters
// the current value of an array as the first parameter
// the position of the current value in an array as the second parameter
// the original source array as the third parameter
const cb = (str, i, origin) => {
  console.log(`${i}: ${Number(str)} / ${origin}`);
};
arr.forEach(cb);
// 0: 1 / 1,2,3
// 1: 2 / 1,2,3
// 2: 3 / 1,2,3

If the thisArg parameter has a value, this will point to the object on the thisArg parameter every time the callback function is called.

If the thisArg parameter is omitted, or the value is null or undefined, then this points to the global object.

For a grudging example, update an object's properties from the element values in each array:

function Counter() {
  this.sum = 0;
  this.count = 0;
}

Counter.prototype.add = function(array) {
  array.forEach(function(entry) {
    this.sum += entry;
    this.count++;
  }, this);
  // console.log(this) -> Counter
};

var obj = new Counter();
obj.add([1, 3, 5, 7]);

obj.count; 
// 4 
obj.sum;
// 16 

If you use the arrow function to pass in a function parameter, the thisArg parameter is ignored because the arrow function lexically binds the this value.

In general, we only use the first two parameters of callback.

map

Map does the same thing as the for loop, except that map creates a new array.

So, if you don't plan to use the new array returned, but still use map, this is against the original intention of map design.

The map function takes two parameters:

  1. callback
    callback also has three parameters:

    1. currentValue
    2. index
    3. array
  2. Thisarg (optional)

This is similar to forEach.

Specific examples:


const arr = ['1', '2', '3'];
// callback function takes 3 parameters
// the current value of an array as the first parameter
// the position of the current value in an array as the second parameter
// the original source array as the third parameter
const cb = (str, i, origin) => {
  console.log(`${i}: ${Number(str)} / ${origin}`);
};
arr.map(cb);
// 0: 1 / 1,2,3
// 1: 2 / 1,2,3
// 2: 3 / 1,2,3

callback method use case:

arr.map((str) => { console.log(Number(str)); })

The result after map is a new array, which is not equal to the original array:

const arr = [1];
const new_arr = arr.map(d => d);
arr === new_arr; // false

You can also specify the value of the second parameter thisArg:

const obj = { name: 'Jane' };

[1].map(function() {
  console.dir(this); // { name: 'Jane' }
}, obj); 

But if you use the arrow function, this will be window:

[1].map(() => {
  console.dir(this);   // window
}, obj);

The working mechanism of arrow function and ordinary function is different, not this scope. You can see this article for details:

https://www.geeksforgeeks.org...

When I write here, I think of a classic topic.

A use skill case

["1", "2", "3"].map(parseInt);

We have all seen this problem. We expect to output [1, 2, 3], but the actual result is [1, NaN, NaN].

Let's briefly analyze the following process. First, let's look at the parameter transfer:

// parseInt(string, radix) -> map(parseInt(value, index))
Index is 0: parseInt ("1, 0); / / 1
 Index is 1: parseInt ("2, 1); / / Nan
 Index is 2: parseInt ("3, 2); / / Nan

Seeing this, the solution is ready:

function returnInt(element) {
  return parseInt(element, 10);
}

['1', '2', '3'].map(returnInt); // [1, 2, 3]

Is it easy to understand?

Get back to the point.

The difference between forEach and map

Look at two lines of code and you'll see:

[1,2,3].map(d => d + 1); // [2, 3, 4];
[1,2,3].forEach(d => d + 1); // undefined;

The author of Vue, you Yuxi, has an image like this:

foreach is what you can do with them one by one in order, whatever you want to do

people.forEach(function (dude) {
  dude.pickUpSoap();
});

A map is a box (a new array) in your hand. One by one, ask them to throw their wallets in. At the end, you get a new array, which contains everyone's wallets. The order of wallets corresponds to the order of people one by one.

var wallets = people.map(function (dude) {
  return dude.wallet;
});

Reduce is when you take your wallet and count one by one to see how much money there is in it? Every time you check one, you add up with the previous sum. At the end of the day, you will know how much money everyone has. var totalMoney = wallets.reduce(function (countedMoney, wallet) {
return countedMoney + wallet.money;
}, 0);

Very close.

Topic link: https://www.zhihu.com/questio...

By the way, send a simple native implementation, and experience the following differences:


Array.prototype.map = function (fn) {
    var resultArray = [];
    for (var i = 0,len = this.length; i < len ; i++) {
         resultArray[i] = fn.apply(this,[this[i],i,this]);
    }
    return resultArray;
}

Array.prototype.forEach = function (fn) {
    for (var i = 0,len = this.length; i < len ; i++) {
         fn.apply(this,[this[i],i,this]);
    }
}

Array.prototype.reduce= function (fn) {
    var formerResult = this[0];
    for (var i = 1,len = this.length; i < len ; i++) {
         formerResult = fn.apply(this,[formerResult,this[i],i,this]);
    }
    return formerResult;
}

Very simple implementation, only to achieve the function, not to do fault-tolerant processing and particularly strict context processing.

When to use map and forEach

Because the difference between the two mainly lies in whether a value is returned, when a new array needs to be generated, map is used, and forEach is used for others

In React, map is also often used to traverse data generation elements:


const people = [
  { name: 'Josh', whatCanDo: 'painting' },
  { name: 'Lay', whatCanDo: 'security' },
  { name: 'Ralph', whatCanDo: 'cleaning' }
];

function makeWorkers(people) {
  return people.map((person) => {
    const { name, whatCanDo } = person;
    return <li key={name}>My name is {name}, I can do {whatCanDo}</li>
  });
}

<ul> {makeWorkers(people)}</ul>

When you don't need to generate new book groups, use forEach:


const mySubjectId = ['154', '773', '245'];

function countSubjects(subjects) {
  let count = 0;
  
  subjects.forEach(subject => {
    if (mySubjectId.includes(subject.id)) {
      count += 1;
    }
  });
  
  return count;
}

const countNumber = countSubjects([
  { id: '223', teacher: 'Mark' },
  { id: '154', teacher: 'Linda' }
]);

countNumber; // 1

This code can also be abbreviated as:

subjects.filter(subject => mySubjectId.includes(subject.id)).length;

Who is faster?

Some people say map is faster, others say forEach is faster, and I'm not sure, so I did a test and got the following results:

Result 1:

Result 2:


The code is almost the same, but the result after running is exactly the opposite.

In fact, we don't have to worry about that fast. Anyway, there is no for fast.

Readability is what we need to consider.

conclusion

Having said a lot, I'm sure you have a clearer understanding of both methods. Let's review the following conclusions:

forEach modifies the original array, and the map method gets a new array and returns it.

So when you need to generate a new array, use map, otherwise use forEach

That's all. I hope you can help me.

Last

If you think the content is helpful, you can focus on my public number "front-end e", learn together and grow together.

Reference resources

https://developer.mozilla.org...
https://s0developer0mozilla0o...
https://jsperf.com/foreach-v-map

Tags: Javascript Attribute Vue React

Posted on Fri, 06 Dec 2019 21:18:07 -0500 by blindtoad