hitomo 2025-10-18 04:30 采纳率: 98.9%
浏览 0
已采纳

如何手动实现Array.prototype.reduce?

如何手动实现 `Array.prototype.reduce`?请考虑初始值存在与不存在的两种情况,并正确处理空数组、单元素数组及遍历时的索引起始位置,同时确保不修改原数组。要求兼容传入或不传初始值的情形,并在每次迭代中按规范调用回调函数(接收累加值、当前项、索引和原数组),最终返回累计结果。注意边界条件判断与错误抛出(如空数组且无初始值时应抛出错误)。
  • 写回答

1条回答 默认 最新

  • 关注

    如何手动实现 Array.prototype.reduce?

    在现代 JavaScript 开发中,Array.prototype.reduce 是函数式编程范式中的核心方法之一。它通过累积的方式将数组“归约”为一个单一值,广泛应用于数据聚合、树结构遍历、状态计算等场景。本文将从基础概念出发,深入剖析其内部机制,并逐步构建一个符合规范的手动实现版本。

    1. 理解 reduce 的基本行为

    reduce 方法接收两个参数:

    • callback:每次迭代执行的函数,接受四个参数:accumulator(累加器)、currentValue(当前元素)、index(当前索引)、array(原数组)。
    • initialValue(可选):作为第一次调用回调时的初始累加值。

    当不提供 initialValue 时,reduce 会以数组的第一个元素作为初始值,并从第二个元素开始遍历。若数组为空且无初始值,则应抛出错误。

    2. 分析关键边界条件

    情况是否传入 initialValue数组长度预期行为
    空数组0抛出 TypeError
    空数组0返回 initialValue
    单元素数组1返回该元素(不执行回调)
    单元素数组1执行一次回调,传入 initialValue 和该元素
    多元素数组n ≥ 2从 index=1 开始遍历,acc = arr[0]
    多元素数组n ≥ 1从 index=0 开始遍历,acc = initialValue

    3. 设计思路与流程图

    ```mermaid
    graph TD
        A[开始] --> B{数组是否为空?}
        B -- 是 --> C{是否有初始值?}
        C -- 否 --> D[抛出 TypeError]
        C -- 是 --> E[返回初始值]
        B -- 否 --> F{提供初始值?}
        F -- 是 --> G[acc = 初始值, i = 0]
        F -- 否 --> H[acc = 第一个元素, i = 1]
        G --> I[循环遍历剩余元素]
        H --> I
        I --> J[调用 callback(acc, current, index, array)]
        J --> K[更新 acc = 返回值]
        K --> L{是否遍历完成?}
        L -- 否 --> I
        L -- 是 --> M[返回最终 acc]
    ```
    

    4. 手动实现 reduce 方法

    以下是一个完整兼容 ECMAScript 规范的 reduce 实现:

    
    function customReduce(array, callback, initialValue) {
      // 类型检查
      if (!Array.isArray(array)) {
        throw new TypeError('Target is not an array');
      }
      if (typeof callback !== 'function') {
        throw new TypeError('Callback must be a function');
      }
    
      const length = array.length;
      let hasInitial = arguments.length > 2;
      let accumulator;
      let startIndex;
    
      // 处理空数组情况
      if (length === 0) {
        if (!hasInitial) {
          throw new TypeError('Reduce of empty array with no initial value');
        }
        return initialValue;
      }
    
      // 确定初始值和起始索引
      if (hasInitial) {
        accumulator = initialValue;
        startIndex = 0;
      } else {
        accumulator = array[0];
        startIndex = 1;
      }
    
      // 遍历并执行回调
      for (let i = startIndex; i < length; i++) {
        if (i in array) { // 支持稀疏数组
          accumulator = callback(accumulator, array[i], i, array);
        }
      }
    
      return accumulator;
    }
    

    5. 测试用例验证实现正确性

    我们通过多个测试用例验证上述实现:

    
    // 测试1:正常求和(无初始值)
    console.log(customReduce([1, 2, 3], (a, b) => a + b)); // 6
    
    // 测试2:带初始值的字符串拼接
    console.log(customReduce(['a', 'b'], (a, b) => a + b, '')); // 'ab'
    
    // 测试3:空数组 + 无初始值 → 抛错
    try {
      customReduce([], () => {});
    } catch (e) {
      console.log(e.message); // "Reduce of empty array with no initial value"
    }
    
    // 测试4:单元素数组(无初始值)
    console.log(customReduce([42], (a, b) => a + b)); // 42
    
    // 测试5:对象数组聚合
    const users = [{age: 25}, {age: 30}, {age: 35}];
    console.log(customReduce(users, (sum, u) => sum + u.age, 0)); // 90
    
    // 测试6:稀疏数组处理
    console.log(customReduce([1,,3], (a,b) => a + b, 0)); // 4
    
    // 测试7:初始值存在但数组为空
    console.log(customReduce([], (a,b) => a + b, 10)); // 10
    
    // 测试8:布尔逻辑归约
    console.log(customReduce([true, false, true], (a, b) => a && b, true)); // false
    
    // 测试9:使用 index 和 array 参数
    customReduce([10, 20], (acc, curr, idx, arr) => {
      console.log(`Index ${idx}: ${curr}, Full array: [${arr}], Acc: ${acc}`);
      return acc + curr;
    }, 0);
    
    // 测试10:不修改原数组
    const original = [1, 2, 3];
    customReduce(original, (a, b) => a + b, 0);
    console.log(original); // [1, 2, 3] —— 未被修改
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

问题事件

  • 已采纳回答 10月23日
  • 创建了问题 10月18日