在现代 JavaScript 开发中,数组处理是一个高频操作。ES6(ECMAScript 2015)引入了许多强大的数组方法,其中 map() 是最常用、也最容易被误解的方法之一。本文将结合实际代码和原理讲解,带你深入理解 map() 的用法、陷阱以及 JavaScript 面向对象式的编程风格。
一、什么是 map()?
定义
Array.prototype.map() 是一个高阶函数 ,它会对原数组中的每个元素 依次调用一个回调函数,并返回一个新数组,新数组的每个元素是回调函数的返回值。
✅ 关键点:
- 不修改原数组(纯函数)
- 返回一个全新数组
- 只对已赋值的索引执行(跳过稀疏数组中的空位)
基本语法
c
const newArray = arr.map(callbackFn(element, index, array), thisArg);
-
callbackFn:处理每个元素的函数element:当前元素index:当前索引(可选)array:原数组(可选)
-
thisArg:可选,指定回调函数中的this值
二、map() 使用示例
示例 1:简单转换
ini
const numbers = [1, 2, 3, 4];
const squares = numbers.map(x => x * x);
console.log(squares); // [1, 4, 9, 16]
console.log(numbers); // [1, 2, 3, 4] ------ 原数组不变
示例 2:对象映射
ini
const users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 }
];
const names = users.map(user => user.name);
console.log(names); // ["Alice", "Bob"]
示例 3:处理稀疏数组
c
const sparse = [1, , 3]; // 第二个位置是空槽
const doubled = sparse.map(x => x * 2);
console.log(doubled); // [2, empty, 6]
// 注意:回调函数不会在空槽上执行
三、经典陷阱:["1", "2", "3"].map(parseInt) 为什么返回 [1, NaN, NaN]?
这是面试高频题!原因在于 map 传递了多个参数,而 parseInt 对第二个参数敏感。
错误写法:
arduino
console.log(["1", "2", "3"].map(parseInt)); // [1, NaN, NaN]
原因分析:
map 调用时实际等价于:
vbscript
parseInt("1", 0, ["1","2","3"]) // → parseInt("1", 0) → 1(基数0按10进制)
parseInt("2", 1, ["1","2","3"]) // → parseInt("2", 1) → NaN(1进制不存在)
parseInt("3", 2, ["1","2","3"]) // → parseInt("3", 2) → NaN(2进制不含3)
正确做法:
scss
// 方式1:显式指定基数
["1", "2", "3"].map(str => parseInt(str, 10)); // [1, 2, 3]
// 方式2:使用 Number 构造函数(更简洁)
["1", "2", "3"].map(Number); // [1, 2, 3]
// 注意:Number 会解析浮点数和科学计数法
["1.1", "2e2"].map(Number); // [1.1, 200]
四、JavaScript 的"面向对象式"编程风格
你可能注意到:
arduino
"hello".length // 字符串有属性?
520.1314.toFixed(2) // 数字能调用方法?
这看起来像面向对象,但 "hello" 是原始类型(primitive) ,不是对象!
包装类(Wrapper Objects)机制
JavaScript 在底层自动将原始类型临时包装为对象,以便调用方法:
arduino
// 实际执行过程(简化版):
// "hello".length
// → new String("hello").length
// → 调用后立即销毁包装对象
支持的包装类:
String→ 字符串Number→ 数字Boolean→ 布尔值
示例验证:
javascript
let str = "hello";
let strObj = new String("hello");
console.log(typeof str); // "string"
console.log(typeof strObj); // "object"
console.log(str.length); // 5(通过包装类实现)
console.log(strObj.length); // 5
💡 提示:这种设计让 JS 既保持简单性,又支持"一切皆对象"的编程体验。
五、关于 NaN 的补充知识
NaN(Not-a-Number)是 JavaScript 中表示"无效数字"的特殊值。
特性:
typeof NaN === "number"(历史遗留问题)NaN !== NaN(唯一不等于自身的值)- 判断
NaN应使用Number.isNaN()或isNaN()
常见产生场景:
javascript
0 / 0; // NaN
Math.sqrt(-1); // NaN
"abc" - 10; // NaN
parseInt("hello"); // NaN
undefined + 5; // NaN
正确判断:
javascript
if (Number.isNaN(parseInt("hello"))) {
console.log("无法解析为数字");
}
六、字符串处理小贴士
虽然不属于 map 主题,但常与数组方法配合使用:
rust
const str = " Hello, 世界! 👋 ";
console.log(str.length); // 13(注意 emoji 占 2 个单位)
console.log(str.slice(1, 6)); // "Hello"
console.log(str.substring(1,6)); // 同上,但不支持负数
// slice vs substring
"hello".slice(-3, -1); // "ll"
"hello".substring(-3, -1); // ""(负数转为0)
📌 UTF-16 编码:常规字符占 1 个单位,emoji/生僻字可能占 2 个或更多。
总结
| 知识点 | 要点 |
|---|---|
map() |
创建新数组,不修改原数组;回调返回值构成新元素 |
| 常见错误 | 避免直接传 parseInt,应封装或用 Number |
| 面向对象 | JS 通过包装类让原始类型也能调用方法 |
NaN |
用 Number.isNaN() 判断,不要用 == 或 === |
| 字符串 | 注意 slice/substring 差异,emoji 长度问题 |
掌握 map() 不仅能写出更简洁的代码,还能避免经典陷阱。结合 JavaScript 独特的面向对象机制,你将更深入理解这门语言的设计哲学。
🌟 最佳实践:
- 用
map做转换 ,用filter做筛选 ,用forEach做副作用操作。- 永远不要忽略回调函数的参数含义!
📚 参考资料:MDN Array.prototype.map()