手写实现 Array.prototype.map 方法

理解并手动实现 JavaScript 数组的 map 方法,掌握数组遍历和回调函数的原理

问题

Array.prototype.map() 是 JavaScript 中常用的数组方法,它会创建一个新数组,其结果是该数组中的每个元素都调用一次提供的函数后的返回值。本题要求手动实现一个功能完整的 map 方法,理解其内部实现原理。

解答

/**
 * 手写实现 Array.prototype.map 方法
 * @param {Function} callback - 回调函数,接收三个参数:当前元素、索引、原数组
 * @param {*} thisArg - 执行 callback 时使用的 this 值
 * @returns {Array} 返回一个新数组
 */
Array.prototype.myMap = function(callback, thisArg) {
  // 1. 处理异常情况
  if (this == null) {
    throw new TypeError('Array.prototype.myMap called on null or undefined');
  }
  
  if (typeof callback !== 'function') {
    throw new TypeError(callback + ' is not a function');
  }
  
  // 2. 将调用对象转换为对象
  const O = Object(this);
  
  // 3. 获取数组长度(无符号右移保证为非负整数)
  const len = O.length >>> 0;
  
  // 4. 创建新数组用于存储结果
  const result = new Array(len);
  
  // 5. 遍历数组,执行回调函数
  for (let i = 0; i < len; i++) {
    // 只处理数组中存在的元素(跳过空位)
    if (i in O) {
      // 调用回调函数,传入当前元素、索引、原数组,并绑定 this
      result[i] = callback.call(thisArg, O[i], i, O);
    }
  }
  
  // 6. 返回新数组
  return result;
};

使用示例

// 示例1:基本使用
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.myMap(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// 示例2:使用索引参数
const arr = ['a', 'b', 'c'];
const withIndex = arr.myMap((item, index) => `${index}-${item}`);
console.log(withIndex); // ['0-a', '1-b', '2-c']

// 示例3:指定 this 上下文
const multiplier = {
  factor: 10,
  multiply: function(num) {
    return num * this.factor;
  }
};
const nums = [1, 2, 3];
const result = nums.myMap(function(num) {
  return this.multiply(num);
}, multiplier);
console.log(result); // [10, 20, 30]

// 示例4:处理稀疏数组
const sparse = [1, , 3]; // 中间有空位
const mapped = sparse.myMap(x => x * 2);
console.log(mapped); // [2, empty, 6]
console.log(mapped.length); // 3

// 示例5:对象数组转换
const users = [
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 30 },
  { name: 'Charlie', age: 35 }
];
const names = users.myMap(user => user.name);
console.log(names); // ['Alice', 'Bob', 'Charlie']

关键点

  • 类型检查:需要检查 this 是否为 null/undefined,以及 callback 是否为函数,不符合则抛出 TypeError

  • 对象转换:使用 Object(this) 将调用对象转换为对象类型,确保可以正常访问属性

  • 长度处理:使用无符号右移 >>> 0 确保长度为非负整数,这是 ECMAScript 规范的标准做法

  • 创建新数组:map 方法不会修改原数组,而是返回一个新数组,长度与原数组相同

  • 稀疏数组处理:使用 in 操作符检查索引是否存在,跳过数组中的空位(empty slots)

  • this 绑定:使用 call 方法调用回调函数,正确绑定 thisArg 参数作为回调函数的 this

  • 回调参数:回调函数接收三个参数:当前元素值、当前索引、原数组对象

  • 返回值:每次回调函数的返回值会作为新数组对应位置的元素