手写实现 Array.prototype.forEach 方法

理解并实现 JavaScript 数组的 forEach 方法,掌握数组遍历的原理

问题

forEach 是 JavaScript 数组的一个常用方法,用于遍历数组中的每个元素并执行回调函数。我们需要手动实现一个功能完整的 forEach 方法,理解其内部工作原理,包括:

  • 遍历数组的每个元素
  • 为回调函数传递正确的参数(当前元素、索引、原数组)
  • 正确处理 this 指向
  • 处理稀疏数组(跳过空位)
  • 返回 undefined

解答

/**
 * 手写实现 forEach 方法
 * @param {Function} callback - 回调函数,接收三个参数:当前元素、索引、原数组
 * @param {*} thisArg - 可选参数,执行回调时的 this 值
 */
Array.prototype.myForEach = function(callback, thisArg) {
  // 1. 检查调用对象是否为 null 或 undefined
  if (this == null) {
    throw new TypeError('Array.prototype.myForEach called on null or undefined');
  }
  
  // 2. 检查 callback 是否为函数
  if (typeof callback !== 'function') {
    throw new TypeError(callback + ' is not a function');
  }
  
  // 3. 将调用对象转换为对象
  const O = Object(this);
  
  // 4. 获取数组长度,使用无符号右移确保为正整数
  const len = O.length >>> 0;
  
  // 5. 遍历数组
  for (let i = 0; i < len; i++) {
    // 6. 只处理数组中实际存在的元素(跳过稀疏数组的空位)
    if (i in O) {
      // 7. 调用回调函数,传入三个参数,并绑定 this
      callback.call(thisArg, O[i], i, O);
    }
  }
  
  // 8. forEach 方法没有返回值(返回 undefined)
  return undefined;
};

使用示例

// 示例 1: 基本使用
const arr1 = [1, 2, 3, 4, 5];
arr1.myForEach((item, index) => {
  console.log(`索引 ${index}: ${item}`);
});
// 输出:
// 索引 0: 1
// 索引 1: 2
// 索引 2: 3
// 索引 3: 4
// 索引 4: 5

// 示例 2: 使用 thisArg 参数
const obj = {
  multiplier: 2,
  multiply: function(arr) {
    arr.myForEach(function(item) {
      console.log(item * this.multiplier);
    }, this); // 传入 this 作为 thisArg
  }
};
obj.multiply([1, 2, 3]);
// 输出: 2, 4, 6

// 示例 3: 处理稀疏数组
const arr2 = [1, , 3, , 5]; // 包含空位的稀疏数组
arr2.myForEach((item, index) => {
  console.log(`索引 ${index}: ${item}`);
});
// 输出:
// 索引 0: 1
// 索引 2: 3
// 索引 4: 5
// 注意:索引 1 和 3 被跳过

// 示例 4: 修改原数组
const arr3 = [1, 2, 3];
arr3.myForEach((item, index, array) => {
  array[index] = item * 2;
});
console.log(arr3); // [2, 4, 6]

// 示例 5: 对比原生 forEach
const arr4 = ['a', 'b', 'c'];
console.log(arr4.myForEach(item => console.log(item))); // undefined
console.log(arr4.forEach(item => console.log(item))); // undefined

关键点

  • 参数校验:需要检查调用对象是否为 null/undefined,以及 callback 是否为函数类型

  • this 绑定:使用 callback.call(thisArg, ...) 来正确绑定回调函数的 this 指向

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

  • 长度处理:使用 >>> 0 无符号右移运算符确保长度为非负整数

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

  • 返回值forEach 方法始终返回 undefined,不支持链式调用

  • 不可中断forEach 无法通过 breakreturn 提前终止循环(这是与 for 循环的重要区别)

  • 类型转换:使用 Object(this) 将调用对象转换为对象类型,确保兼容性