JavaScript 高阶函数-map
目录
map是 JavaScript 数组的高阶函数,以下从定义、用法、核心特性、实际示例、注意事项几个方面讲清楚,新手也能轻松理解,最后还会总结核心要点。
一、map 是什么?
map 是 JavaScript 数组的高阶函数(接收函数作为参数的函数),核心作用是:遍历数组的每一个元素,对每个元素执行指定的处理函数,最后返回一个「新数组」,新数组的元素是原数组元素处理后的结果。
简单说:map 就是「数组的批量加工器」,不改变原数组,只返回加工后的新数组。
二、基本语法
array.map(function(currentValue,index,arr), thisValue)
| 参数 | 描述 | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| function(currentValue, index,arr) | 必须。函数,数组中的每个元素都会执行这个函数 函数参数:
| ||||||||
| thisValue | 可选。对象作为该执行回调时使用,传递给函数,用作 "this" 的值。 如果省略了 thisValue,或者传入 null、undefined,那么回调函数的 this 为全局对象。 |
| 返回值: | 返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。 |
|---|
原数组.map(function(当前元素, 索引, 原数组) { // 对当前元素的处理逻辑 return 处理后的结果; // 必须有return,否则新数组元素为undefined }, 可选的this指向); // 更常用的箭头函数写法(简洁,推荐) 原数组.map((item, index, arr) => { return 处理后的结果; }); // 若处理逻辑只有一行,可省略大括号和return(箭头函数简写) 原数组.map(item => 处理后的结果);参数说明(前 3 个是处理函数的参数,最常用前 2 个)
- item(必选):当前正在遍历的数组元素;
- index(可选):当前元素的索引(从 0 开始);
- arr(可选):调用
map的原数组本身; - 可选的 this 指向(极少用):指定处理函数内部
this的指向(箭头函数不绑定 this,此参数对箭头函数无效)。
三、核心特性(重点)
- 返回新数组:不会修改原数组,纯函数特性(无副作用),这是和
for循环直接修改原数组的核心区别; - 长度不变:新数组的长度和原数组完全一致,哪怕某个元素处理后返回
undefined,新数组对应位置也会是undefined; - 必须有 return:如果处理函数没有
return,新数组的所有元素都会是undefined; - 遍历所有元素:不会跳过数组中的任何元素(包括
undefined/null)。
四、实际示例(从简单到复杂)
结合实际场景,看 map 的常用用法,对比传统 for 循环,能更直观感受到 map 的简洁性。
示例 1:基础用法 —— 数组元素翻倍
将数组中每个数字乘以 2,返回新数组。
// 原数组 const nums = [1, 2, 3, 4]; // map 处理 const newNums = nums.map(item => item * 2); console.log(原数组: nums); // 原数组: [1,2,3,4](未修改) console.log(新数组: newNums); // 新数组: [2,4,6,8]对比 for 循环:需要手动创建空数组、遍历、push,代码更繁琐:
const nums = [1, 2, 3, 4]; const newNums = []; for (let i = 0; i < nums.length; i++) { newNums.push(nums[i] * 2); }示例 2:使用索引 —— 给数组元素加索引标识
const fruits = ['苹果', '香蕉', '橙子']; // 结合index,生成「索引-水果」的新数组 const fruitList = fruits.map((item, index) => `${index+1}-${item}`); console.log(fruitList); // ["1-苹果", "2-香蕉", "3-橙子"]示例 3:处理对象数组(开发最常用)
实际开发中,常遇到接口返回的对象数组,用 map 提取需要的属性,或加工对象属性,非常方便。
// 接口返回的用户数组(原数据) const users = [ { id: 1, name: '张三', age: 20 }, { id: 2, name: '李四', age: 25 }, { id: 3, name: '王五', age: 30 } ]; // 需求1:只提取所有用户的name,生成姓名数组 const userNames = users.map(item => item.name); console.log(userNames); // ["张三", "李四", "王五"] // 需求2:加工对象,给每个用户加「是否成年」的属性,返回新对象数组 const newUsers = users.map(item => ({ ...item, // 解构原对象,保留原有属性 isAdult: item.age >= 18 // 新增属性 })); console.log(newUsers); // 结果:[{id:1, name:'张三', age:20, isAdult:true}, ...]注意:对象数组的简写中,返回对象需要加小括号(),否则 JS 会把大括号{}当作函数体,而非对象。
示例 4:省略 return 的坑
如果处理函数没有 return,新数组所有元素都是 undefined,一定要避免:
const nums = [1,2,3]; const badNums = nums.map(item => { item * 2 }); // 无return console.log(badNums); // [undefined, undefined, undefined]示例 5:占位符
下划线 _ 来作为占位符
const newArr = arr.map((_, index) => index);五、map 与 forEach 的区别(易混淆,必看)
很多新手会把 map 和 forEach 搞混,两者都能遍历数组,但核心区别是是否有返回值,适用场景不同:
| 特性 | map | forEach |
|---|---|---|
| 返回值 | 有,返回新数组 | 无,返回 undefined |
| 核心用途 | 对数组元素做加工转换,需要新数组 | 单纯的遍历执行操作(如打印、修改外部变量) |
| 是否可链式调用 | 可以(如 map ().filter ()) | 不可以(无返回值) |
示例对比:
const nums = [1,2,3]; // map:有返回值,可链式调用 nums.map(item => item*2).filter(item => item > 3); // [4,6] // forEach:无返回值,链式调用会报错 nums.forEach(item => item*2).filter(...) // Uncaught TypeError: Cannot read property 'filter' of undefined六、注意事项
- 不修改原数组:
map的设计初衷是纯函数,尽量不要在处理函数中修改原数组的元素(尤其是对象 / 数组元素,因为引用类型会浅拷贝,后续单独讲); - 遍历不可中断:
map会遍历数组所有元素,无法像for循环那样用break/continue中断遍历,如果需要中断,用filter先过滤,或直接用for循环; - 浅拷贝:如果原数组是引用类型数组(对象 / 数组),
map返回的新数组中,元素是原对象的浅拷贝(即新数组和原数组指向同一个对象),修改新数组的对象属性,会影响原数组:
const arr = [{a: 1}]; const newArr = arr.map(item => item); newArr[0].a = 2; // 修改新数组的对象属性 console.log(arr[0].a); // 2(原数组也被修改了)- 解决:在
map中返回新对象(如示例 3 的解构...item),实现深拷贝(简单场景)。
4.避免空值遍历:如果数组中有空值(empty),map 会跳过,但仍会在新数组中保留空值位置:
const arr = [1, , 3]; // 中间有一个空值 const newArr = arr.map(item => item * 2); console.log(newArr); // [2, , 6]总结
map是数组高阶函数,核心是遍历 + 加工 + 返回新数组,不修改原数组,长度和原数组一致;- 语法上推荐箭头函数简写,处理函数必须有 return,否则新数组元素为
undefined; - 开发中最常用在加工普通数组、提取 / 修改对象数组属性,比 for 循环更简洁;
- 与
forEach核心区别:map有返回值(新数组)可链式调用,forEach无返回值仅做遍历; - 注意引用类型数组的浅拷贝问题,如需独立对象,在 map 中返回新对象即可。