js中forEach & map

更新日期: 2019-11-01阅读: 2.3k标签: map

背景

JavaScript中,数组的遍历我们肯定都不陌生,最常见的两个便是forEach 和 map。

(当然还有别的譬如for, for in, for of, reduce, filter, every, some, ...)

之所以几天要写这个, 是因为前几天写代码的时候犯了一个低级且愚蠢的错误, 最后搞出了个小bug。

最后找到原因, 生气, 甚至还有点想笑, 今天就写一下这两个方法。


正文

我扑街的代码是这样的, 要给一个数组中的对象加一个属性, 我随手就写了如下代码:

// 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);

后面发现, 数组是空的... 囧

感觉自己真蠢, 改一下:

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

搞定。

我们仔细看一下forEach 和 map 这两个方法:


对比和结论

forEach: 针对每一个元素执行提供的函数

map: 创建一个新的数组,其中每一个元素由调用数组中的每一个元素执行提供的函数得来。

直接说结论吧:

forEach方法不会返回执行结果,而是undefined。

也就是说,forEach会修改原来的数组,而map方法会得到一个新的数组并返回。

下面我们看下具体的例子。


forEach

forEach 方法按升序为数组中含有效值的每一项执行一次callback 函数,那些已删除或者未初始化的项将被跳过(例如在稀疏数组上)。

forEach 接收两个参数: arr.forEach(callback[, thisArg]);

callback 函数会被依次传入三个参数:

  1. 数组当前项的值
  2. 数组当前项的索引
  3. 数组对象本身

比如:

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

如果 thisArg 参数有值,则每次 callback 函数被调用的时候,this 都会指向 thisArg 参数上的这个对象。

如果省略了 thisArg 参数, 或者赋值为 null 或 undefined,则 this 指向全局对象。

举个勉强的例子,从每个数组中的元素值中更新一个对象的属性:

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 

如果使用箭头函数来传入函数参数,thisArg 参数会被忽略,因为箭头函数在词法上绑定了 this 值。

一般情况下, 我们只会用到callback的前两个参数。


map

map 做的事情和 for循环 一样,不同的是, map 会创建一个新数组。

所以, 如果你不打算使用返回的新数组, 却依旧使用map的话, 这是违背map的设计初衷的。

map 函数接收两个参数:

  1. callback
    callback 也有三个参数:

    1. currentValue
    2. index
    3. array
  2. thisArg(可选)

这一点和forEach 是类似的。

具体的例子:


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 方法用例:

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

map 之后的结果是一个新数组, 和原数组是不相等的:

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

你也可以指定第二个参数thisArg的值:

const obj = { name: 'Jane' };

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

但是如果你使用的是箭头函数, 此时的 this将会是 window:

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

箭头函数和普通函数的工作机制是不同的,不是本范围,你可以看这篇文章了解具体细节:https://www.geeksforgeeks.org...

写到这里, 就想起来一个经典的题目。


一个使用技巧案例

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

这道题我们都见过, 我们期望输出 [1, 2, 3], 而实际结果是 [1, NaN, NaN]。

我们简单来分析下过程, 首先看一下参数传递情况:

// 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

看到这, 解决办法也就呼之欲出了:

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

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

是不是很容易理解?

回到正题。


forEach 和 map 的区别

看两行代码你就懂了:

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

vue作者,尤雨溪大佬,有这么一个形象的比方:

foreach 就是你按顺序一个一个跟他们做点什么,具体做什么,随便:

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

map 就是你手里拿一个盒子(一个新的数组),一个一个叫他们把钱包扔进去。结束的时候你获得了一个新的数组,里面是大家的钱包,钱包的顺序和人的顺序一一对应。

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

reduce 就是你拿着钱包,一个一个数过去看里面有多少钱啊?每检查一个,你就和前面的总和加一起来。这样结束的时候你就知道大家总共有多少钱了。

var totalMoney = wallets.reduce(function (countedMoney, wallet) {
   return countedMoney + wallet.money;
}, 0);

十分的贴切。

话题链接:https://www.zhihu.com/questio...

顺便送一段简单的原生实现, 感受下区别:


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;
}

很简单的实现,仅仅实现功能,没做容错处理和特别严格的上下文处理。


什么时候使用 map 和 forEach

因为这两个的区别主要在于是不是返回了一个值, 所以需要生成新数组的时候, 就用map, 其他的就用forEach.

react 中, map 也经常被用来遍历数据生成元素:


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>

当你不需要生成新书组的时候,用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

这段代码也可以简写为:

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

谁更快

有些人说map 更快, 也有人说forEach 更快, 我也不确定, 所以就做了个测试,代码几乎都是一样的, 但是运行之后的结果恰恰相反。

其实吧, 我们不用纠结到底那个快,反正,都没有for快。

可读性, 才是我们要考虑的。


结论

说了一大堆, 相信大家肯定对这两个方法都有更清楚的认知了,我们再回顾下结论:

forEach 会修改原来的数组,而map方法会得到一个新的数组并返回。

所以需要生成新数组的时候, 就用map, 否则就用forEach.

以上就是全部内容,希望多大家有所帮助。

链接: https://fly63.com/article/detial/6696

JS 中为啥 [‘1‘, ‘7’,‘11’ ].map(parseInt) 返回 [1, NaN, 3]

Javascript 一直是神奇的语言。 不相信我? 尝试使用map和parseInt将字符串数组转换为整数。打开 Chrome 的控制台(F12),粘贴以下内容,然后按回车,查看输出结果:

为什么 [‘1’, ‘7’, ‘11’].map(parseInt) 的结果是 [1, NaN, 3]?

在 Javascript 中,一个函数可以传递任何多个数量的参数,即使调用时传递的数量与定义时的数量不一致。缺失的参数会以 undefined 作为实际值传递给函数体,然后多余的参数会直接被忽略掉

js中Map

在JavaScript中,Map 是存储键/值对的对象。Map 类似于一般 JavaScript 对象 ,但对象与 Map 之间一些关键的差异使 Map 很有用。如果你要创建一个存储一些键/值路径的 JavaScript 对象

typescript中继承Array、Map、Set报错的解决

Map、Set的polyfill实现是可以继承的;//可继承的Array替换原生Array,Array要改的地比较多,除了替换原生Array还需修改继承函数,供参考

用map代替纯JavaScript对象

普通的 JavaScript 对象通常可以很好地保存结构化数据。但是它们有一些限制:只能用字符串或符号用作键,自己的对象属性可能会与从原型继承的属性键冲突(例如,toString,constructor 等)。对象不能用作键

ES6的Map类型

Map的出现解决了传统object无法直接解决的问题,更好地向标准编程语言靠近(标准编程语言一般会提供Map集合),使用的坑也比较少(比如没有object作为key时转换为[object Object]的问题)。

es6 Set和Map数据结构

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。

ES6 Map 原理

ES6的Map的键可以是任意的数据结构,并且不重复。那么map的底层原理是啥呢?Map利用链表,hash的思想来实现。首先,Map可以实现删除,而且删除的数据可以是中间的值。

何时使用 Map 来代替普通的 JS 对象

JS 普通对象 {key: value} 用于存放结构化数据。但有一件事我觉得很烦:对象键必须是字符串(或很少使用的 symbol)。如果将数字用作键会怎样? 在这种情况下不会有错误:

Js如何将JSON对象转为map对象?

JSON 对象保存在大括号内。就像在JavaScript中, 对象可以保存多个 键/值 对。Map对象保存键/值对,是键/值对的集合。任何值(对象或者原始值) 都可以作为一个键或一个值。Object结构提供了“字符串—值”的对应

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!