从 Map 说起:ES6 数组转换的魔法工具与避坑指南

119 阅读4分钟

一、Map 与 forEach:数组迭代的「孪生兄弟」

1.1 核心差异:「生产型」vs「消费型」

Map 就像一个「数组加工厂」,接收一个处理函数后会「生产」出一个全新数组,原数组则保持「完璧之身」。比如将数组元素翻倍:

const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2); 
// doubled: [2, 4, 6],原数组numbers依然是[1, 2, 3]

而 forEach 更像是「数组观光团」,专注于对每个元素「执行操作」,但不会产出新数组,返回值永远是undefined。比如打印元素:

const words = ['hello', 'world'];
words.forEach(word => console.log(word.toUpperCase())); 
// 控制台输出HELLO和WORLD,但返回值是undefined

简单来说:想「生成新数组」就用 Map,想「执行副作用操作」就选 forEach。

1.2 参数细节:隐藏的「第三个小伙伴」

两者的回调函数都支持三个参数:当前值item、索引index、原始数组arr。但 Map 的「返回值」会被收集成新数组,而 forEach 的返回值会被「默默忽略」。比如:

const scores = [80, 90, 85];
// Map会收集return的结果
const graded = scores.map((score, index) => score + (index === 0 ? 5 : 0)); 
// [85, 90, 85]

// forEach的return毫无作用
const ignored = scores.forEach((score, index) => score + (index === 0 ? 5 : 0)); 
// ignored是undefined

这里有个小口诀:Map「有去有回」,forEach「有去无回」~

二、Map 参数解析:别让「隐形参数」坑了你

2.1 回调函数的「完整签名」

Map 的完整语法是arr.map(callback[, thisArg]),第二个参数thisArg用于指定回调函数内的this值,默认是window。比如:

const user = { name: 'Alice' };
const greetings = ['hi', 'hello', 'hey'].map(function(word) {
  return word + ', ' + this.name; 
}, user); 
// ['hi, Alice', 'hello, Alice', 'hey, Alice']

如果忽略thisArg,在严格模式下this会是undefined,可能导致意想不到的错误哦~

2.2 当 Map 遇见 parseInt:经典「NaN 陷阱」

重点来了!看下面这个经典例子:

console.log(['1', '2', '3'].map(parseInt)); 
// 输出:[1, NaN, NaN]

为什么会这样?因为 Map 传递给回调函数的参数是(value, index, arr),而 parseInt 的语法是parseInt(string, radix),这里的index变成了radix参数:

  • 第一个元素:parseInt('1', 0)radix为 0 时默认按 10 进制解析,结果 1

  • 第二个元素:parseInt('2', 1)radix为 1 是无效进制(2-36 才有效),结果 NaN

  • 第三个元素:parseInt('3', 2),二进制中没有 3,解析到 3 时停止,结果 NaN

正确的做法是显式传递处理函数,避免索引干扰:

const safeParse = (str) => parseInt(str, 10); 
console.log(['1', '2', '3'].map(safeParse)); 
// 正确输出:[1, 2, 3]

记住:当使用原生函数作为 Map 的回调时,一定要检查参数匹配是否正确!

三、最佳实践:写出「健壮且优雅」的 Map 代码

3.1 避免「隐式类型转换」

始终为 parseInt 指定radix: 10,避免浏览器差异:

// 反例:依赖默认radix,可能解析为八进制或十六进制
['010', '0x10'].map(parseInt); 
// 输出:[8, 16](不同浏览器可能有不同结果)

// 正例:显式指定十进制
['010', '0x10'].map(str => parseInt(str, 10)); 
// 输出:[10, 10]

3.2 处理「复杂逻辑」时拆分函数

如果回调函数逻辑复杂,建议拆分成独立函数,提高可读性:

// 反例:单行箭头函数变得臃肿
const numbers = ['12', '34', '56'];
const processed = numbers.map(n => n.padStart(3, '0').slice(-2)); 

// 正例:拆分成独立处理函数
function processNumber(n) {
  return n.padStart(3, '0').slice(-2); 
}
const processed = numbers.map(processNumber); 

3.3 结合「链式调用」提升效率

Map 返回的新数组可以无缝衔接其他数组方法(如 filter、reduce),实现流式处理:

const rawData = [' 15 ', '20px', '-25', '30'];
const total = rawData
  .map(str => str.trim()) // 去除空格
  .filter(str => /^\d+/.test(str)) // 过滤有效数字字符串
  .map(str => parseInt(str, 10)) // 转换为数字
  .reduce((sum, num) => sum + num, 0); // 求和
// total: 65

这样的链式写法既优雅又高效,是前端处理数据的常用套路~

四、总结:Map 的「魔法口诀」

  • 生成新数组,Map 来帮忙;执行副作用,forEach 别忘

  • 回调参数要记牢,value、index、原数组一个不少

  • parseInt 遇 Map,radix 陷阱要避开,显式指定 10 进制最安全

  • 链式调用真方便,数据处理变简单

掌握了这些,你就能在数组转换的世界里游刃有余啦~下次遇到类似的问题,记得想想 Map 的这些特性,让代码既正确又优雅!