JavaScript 学习笔记:深入理解 map() 方法与面向对象特性

67 阅读3分钟

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]

最佳实践:避免直接传递 parseIntmap,始终明确指定进制或使用 Number


三、JavaScript 的面向对象特性与包装类

尽管 JavaScript 拥有原始数据类型(如 stringnumber),但它在设计上全面支持面向对象编程。例如:

"hello".length;        // 5
114.514.toFixed(2);    // "114.51"

这些看似“不可能”的调用之所以成立,得益于 包装类(Wrapper Objects) 机制。

3.1 包装类原理

当对原始类型调用方法时,JavaScript 引擎会临时创建对应的包装对象,执行方法后立即销毁:

// 等价于:
const temp = new String("hello");
temp.length; // 5
// temp 被自动回收

常见的包装类包括:

  • String
  • Number
  • Boolean

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 的特性

  • NaNnumber 类型;
  • 任何涉及 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() 是不可变数据转换的强大工具,但需注意回调函数的参数传递;
  • parseIntmap 结合时务必显式指定进制;
  • JavaScript 通过包装类实现“原始类型也能调方法”的面向对象风格;
  • 字符串方法中,slice()substring() 更可靠;
  • 理解 NaN 和 Unicode 编码有助于避免常见陷阱。

掌握这些核心概念,不仅能写出更健壮的代码,还能深入理解 JavaScript 的设计哲学——灵活性与易用性并重