Array.prototype.map
map 对数组每个元素执行回调,返回新数组;回调自身仍可能修改原数组。回调接收 (value, index, array) 三个参数。
#frontend
#concept
#evergreen
#javascript
#ecmascript
[!info] related notes
- 相关概念: ecmascript-collection-reference-types, higher-order-function-callback-mismatch, parseint
- 对比方法:
forEach,filter,reduce
Array.prototype.map
一句话定义
map 对数组每个元素执行回调函数,将回调的返回值组成新数组返回。map 本身不直接改写输入,但回调可修改原数组或其中的对象。
签名
array.map(callbackFn)
array.map(callbackFn, thisArg)
callbackFn 参数
这是最容易被忽略的关键点。 回调函数会收到 3 个参数,而不是只有值:
array.map(function callback(currentValue, index, array) {
// currentValue : 当前元素
// index : 当前索引
// array : 原数组本身
});
| 参数 | 含义 |
|---|---|
currentValue | 当前正在处理的元素 |
index | 当前元素的索引 |
array | 调用 map 的数组本身 |
执行逻辑
- 读取初始 length,并据此创建结果数组(以下讨论普通 Array)
- 按索引从
0到length - 1依次遍历 - 对访问时存在的索引属性调用回调,也包括继承的数字索引属性
- 将回调的返回值填入新数组的对应位置
- 跳过不存在的索引;若原型链上有对应数字属性,就不算缺失
const arr = [1, , 3];
arr.map(x => x * 2); // [2, empty, 6] ← 空位被跳过
对比显式 undefined:
const arr = [1, undefined, 3];
arr.map(x => x * 2); // [2, NaN, 6] ← undefined 会被处理
基本用法
[1, 2, 3].map(x => x * 2) // [2, 4, 6]
['a', 'b', 'c'].map(s => s.toUpperCase()) // ['A', 'B', 'C']
[1, 2, 3].map((x, i) => `${i}: ${x}`) // ['0: 1', '1: 2', '2: 3']
返回值
对普通数组返回一个新数组,长度由调用开始时读取的 length 决定;空位仍计入长度。遍历期间追加到初始长度之外的元素不会被访问,尚未访问的位置若被删除且原型链上也不存在对应属性,则会跳过。
const original = [1, 2, 3];
const mapped = original.map(x => x * 2);
console.log(mapped); // [2, 4, 6]
console.log(original); // [1, 2, 3](不变)
thisArg
可选的第二个参数,指定回调中 this 的值:
const obj = { factor: 10 };
[1, 2, 3].map(function(x) {
return x * this.factor;
}, obj); // [10, 20, 30]
注意:箭头函数没有自己的 this 绑定,使用外层词法 this,因此 thisArg 不能改变它。
经典陷阱:map(parseInt)
详见 higher-order-function-callback-mismatch。
['1', '2', '3'].map(parseInt)
// 实际执行:parseInt('1', 0), parseInt('2', 1), parseInt('3', 2)
// 结果:[1, NaN, NaN]
原因:map 会传 (value, index),而 parseInt 的第二参数是 radix(进制),index=1 是非法进制。
与相关方法对比
| 方法 | 返回值 | 跳过空位 | 修改原数组 |
|---|---|---|---|
map | 新数组 | 是 | 否 |
forEach | undefined | 是 | 否(但回调里可以改) |
filter | 新数组(子集) | 是 | 否 |
reduce | 累积值 | 是 | 否 |
表中的“不修改”仅指方法自身;所有回调仍可能产生副作用。映射对象也不自动深拷贝。