手写实现 Array.prototype.reduce 方法

从零实现 JavaScript 数组的 reduce 方法,理解其工作原理和应用场景

问题

reduce 是 JavaScript 数组中非常强大的方法,它可以将数组元素通过回调函数累积为单个值。我们需要手动实现一个功能完整的 reduce 方法,支持:

  • 累加器的初始值设置
  • 回调函数接收累加器、当前值、当前索引、原数组
  • 处理空数组的边界情况
  • 支持链式调用

解答

/**
 * 手写实现 Array.prototype.reduce 方法
 * @param {Function} callback - 回调函数
 * @param {*} initialValue - 初始值(可选)
 * @returns {*} 累积计算的结果
 */
Array.prototype.myReduce = function(callback, initialValue) {
  // 检查调用对象是否为 null 或 undefined
  if (this == null) {
    throw new TypeError('Array.prototype.myReduce called on null or undefined');
  }
  
  // 检查 callback 是否为函数
  if (typeof callback !== 'function') {
    throw new TypeError(callback + ' is not a function');
  }
  
  // 转换为对象
  const arr = Object(this);
  const len = arr.length >>> 0; // 确保长度为非负整数
  
  let index = 0; // 当前索引
  let accumulator; // 累加器
  
  // 判断是否提供了初始值
  if (arguments.length >= 2) {
    // 提供了初始值
    accumulator = initialValue;
  } else {
    // 没有提供初始值,需要找到数组中第一个存在的元素作为初始值
    if (len === 0) {
      throw new TypeError('Reduce of empty array with no initial value');
    }
    
    // 找到第一个存在的元素
    let found = false;
    while (index < len) {
      if (index in arr) {
        accumulator = arr[index];
        index++;
        found = true;
        break;
      }
      index++;
    }
    
    if (!found) {
      throw new TypeError('Reduce of empty array with no initial value');
    }
  }
  
  // 遍历数组,执行回调函数
  while (index < len) {
    // 只处理数组中实际存在的元素(跳过空位)
    if (index in arr) {
      accumulator = callback(accumulator, arr[index], index, arr);
    }
    index++;
  }
  
  return accumulator;
};

使用示例

// 示例1: 数组求和
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.myReduce((acc, cur) => acc + cur, 0);
console.log(sum); // 输出: 15

// 示例2: 数组求积
const product = numbers.myReduce((acc, cur) => acc * cur, 1);
console.log(product); // 输出: 120

// 示例3: 数组扁平化
const nested = [[1, 2], [3, 4], [5, 6]];
const flattened = nested.myReduce((acc, cur) => acc.concat(cur), []);
console.log(flattened); // 输出: [1, 2, 3, 4, 5, 6]

// 示例4: 统计元素出现次数
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const count = fruits.myReduce((acc, cur) => {
  acc[cur] = (acc[cur] || 0) + 1;
  return acc;
}, {});
console.log(count); // 输出: { apple: 3, banana: 2, orange: 1 }

// 示例5: 对象属性求和
const items = [
  { name: 'item1', price: 10 },
  { name: 'item2', price: 20 },
  { name: 'item3', price: 30 }
];
const totalPrice = items.myReduce((acc, item) => acc + item.price, 0);
console.log(totalPrice); // 输出: 60

// 示例6: 不提供初始值
const nums = [1, 2, 3, 4];
const result = nums.myReduce((acc, cur) => acc + cur);
console.log(result); // 输出: 10

// 示例7: 数组去重
const duplicates = [1, 2, 2, 3, 3, 4, 5, 5];
const unique = duplicates.myReduce((acc, cur) => {
  if (!acc.includes(cur)) {
    acc.push(cur);
  }
  return acc;
}, []);
console.log(unique); // 输出: [1, 2, 3, 4, 5]

关键点

  • 参数校验:检查 this 是否为 null/undefined,检查 callback 是否为函数类型

  • 初始值处理:通过 arguments.length 判断是否提供了初始值,未提供时需要使用数组第一个存在的元素

  • 空数组处理:当数组为空且没有提供初始值时,应抛出 TypeError 异常

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

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

  • 回调函数参数:正确传递四个参数:累加器(accumulator)、当前值(currentValue)、当前索引(currentIndex)、原数组(array)

  • 累加器更新:每次迭代都要用回调函数的返回值更新累加器

  • 返回值:最终返回累加器的值,可以是任意类型(数字、字符串、对象、数组等)