JavaScript 学习笔记:深入理解 map() 方法与面向对象特性
一、Array.prototype.map() 方法详解
map() 是 ES6 中引入的数组高阶函数之一,用于对数组中的每个元素执行指定操作,并返回一个全新数组。其核心特点是不修改原数组,而是基于回调函数的返回值构建新数组。
1.1 基本语法
const newArray = arr.map(callbackFn(element, index, array), thisArg);
-
callbackFn:为每个元素调用的函数,接收三个参数:element:当前元素;index:当前索引;array:原数组本身。
-
thisArg(可选) :指定回调函数内部的this值。
1.2 使用示例
const numbers = [1, 4, 9];
const roots = numbers.map(num => Math.sqrt(num)); // [1, 2, 3]
const doubles = numbers.map(num => num * 2); // [2, 8, 18]
注意:map() 不会改变原数组,也不会遍历稀疏数组中的“空槽”。
二、经典陷阱:map(parseInt) 的误区
一个常见的错误是直接将 parseInt 作为 map 的回调函数:
console.log(["1", "2", "3"].map(parseInt)); // [1, NaN, NaN]
2.1 原因分析
parseInt(string, radix) 接收两个参数:
string:要解析的字符串;radix:进制基数(2–36)。
而 map() 会传递三个参数给回调函数:(element, index, array)。因此实际调用如下:
parseInt("1", 0); // 1(radix=0 被忽略,默认十进制)
parseInt("2", 1); // NaN(1 进制非法)
parseInt("3", 2); // NaN(2 进制不含数字 3)
2.2 正确写法
// 方式一:显式指定基数
["1", "2", "3"].map(str => parseInt(str, 10)); // [1, 2, 3]
// 方式二:使用 Number 构造函数(更简洁)
["1", "2", "3"].map(Number); // [1, 2, 3]
// 注意:Number 会解析浮点数和科学计数法
["1.1", "2.2e2"].map(Number); // [1.1, 220]
✅ 最佳实践:避免直接传递
parseInt给map,始终明确指定进制或使用Number。
三、JavaScript 的面向对象特性与包装类
尽管 JavaScript 拥有原始数据类型(如 string、number),但它在设计上全面支持面向对象编程。例如:
"hello".length; // 5
114.514.toFixed(2); // "114.51"
这些看似“不可能”的调用之所以成立,得益于 包装类(Wrapper Objects) 机制。
3.1 包装类原理
当对原始类型调用方法时,JavaScript 引擎会临时创建对应的包装对象,执行方法后立即销毁:
// 等价于:
const temp = new String("hello");
temp.length; // 5
// temp 被自动回收
常见的包装类包括:
StringNumberBoolean
3.2 类型检测
const str = "hello";
const strObj = new String("hello");
console.log(typeof str); // "string"
console.log(typeof strObj); // "object"
console.log(str === strObj); // false(类型不同)
⚠️ 注意:尽量避免手动创建包装对象(如
new String()),这会导致不必要的性能开销和类型混淆。
四、字符串处理方法对比:slice() vs substring()
4.1 slice(start, end)
- 支持负数索引(从末尾计数);
- 不交换参数顺序;
- 若
start > end,返回空字符串。
"Hello".slice(-3, -1); // "ll"
"Hello".slice(3, 1); // ""
4.2 substring(start, end)
- 不支持负数(负值转为 0);
- 自动交换参数使
start ≤ end。
"Hello".substring(-3, -1); // ""(等价于 substring(0, 0))
"Hello".substring(3, 1); // "el"(等价于 substring(1, 3))
✅ 建议:优先使用
slice(),行为更符合直觉。
五、其他实用知识点
5.1 NaN 的特性
NaN是number类型;- 任何涉及
NaN的算术运算结果仍是NaN; NaN !== NaN,需用isNaN()或Number.isNaN()判断。
console.log(typeof NaN); // "number"
console.log(NaN === NaN); // false
console.log(isNaN("abc")); // true
console.log(Number.isNaN(NaN)); // true
5.2 字符串长度与 Unicode
JavaScript 使用 UTF-16 编码,大多数字符占 1 个单位,但某些字符(如 emoji、生僻字)占 2 个或更多:
console.log("a".length); // 1
console.log("中".length); // 1
console.log("𝄞".length); // 2(音乐符号)
console.log("👋".length); // 2(emoji)
💡 处理多字节字符时,建议使用
Array.from(str).length获取真实字符数。
六、总结
map()是不可变数据转换的强大工具,但需注意回调函数的参数传递;parseInt与map结合时务必显式指定进制;- JavaScript 通过包装类实现“原始类型也能调方法”的面向对象风格;
- 字符串方法中,
slice()比substring()更可靠; - 理解
NaN和 Unicode 编码有助于避免常见陷阱。
掌握这些核心概念,不仅能写出更健壮的代码,还能深入理解 JavaScript 的设计哲学——灵活性与易用性并重。