reduce用法汇总

全面总结JavaScript中Array.prototype.reduce()方法的常见用法和实战技巧

问题

reduce() 是 JavaScript 数组方法中最强大但也最容易被忽视的方法之一。它可以将数组元素通过累加器函数处理,最终归纳为单个值。本文汇总 reduce 的常见用法和实战场景。

解答

1. 基础语法

// reduce 基础语法
// array.reduce(callback(accumulator, currentValue, currentIndex, array), initialValue)

// 参数说明:
// - accumulator: 累加器,累积回调的返回值
// - currentValue: 当前处理的元素
// - currentIndex: 当前元素的索引(可选)
// - array: 调用 reduce 的数组(可选)
// - initialValue: 初始值(可选)

2. 数组求和

// 数组求和
const sum = (arr) => {
  return arr.reduce((acc, cur) => acc + cur, 0);
};

// 示例
console.log(sum([1, 2, 3, 4, 5])); // 15

3. 数组求积

// 数组求积
const product = (arr) => {
  return arr.reduce((acc, cur) => acc * cur, 1);
};

// 示例
console.log(product([1, 2, 3, 4])); // 24

4. 数组扁平化

// 一维数组扁平化
const flatten = (arr) => {
  return arr.reduce((acc, cur) => {
    return acc.concat(Array.isArray(cur) ? flatten(cur) : cur);
  }, []);
};

// 示例
console.log(flatten([1, [2, [3, [4]], 5]])); // [1, 2, 3, 4, 5]

5. 数组去重

// 数组去重
const unique = (arr) => {
  return arr.reduce((acc, cur) => {
    return acc.includes(cur) ? acc : [...acc, cur];
  }, []);
};

// 示例
console.log(unique([1, 2, 2, 3, 3, 4])); // [1, 2, 3, 4]

6. 计数/分组

// 统计元素出现次数
const countOccurrences = (arr) => {
  return arr.reduce((acc, cur) => {
    acc[cur] = (acc[cur] || 0) + 1;
    return acc;
  }, {});
};

// 示例
console.log(countOccurrences(['a', 'b', 'a', 'c', 'b', 'a']));
// { a: 3, b: 2, c: 1 }

// 按条件分组
const groupBy = (arr, key) => {
  return arr.reduce((acc, cur) => {
    const groupKey = typeof key === 'function' ? key(cur) : cur[key];
    (acc[groupKey] = acc[groupKey] || []).push(cur);
    return acc;
  }, {});
};

// 示例
const students = [
  { name: '张三', age: 20 },
  { name: '李四', age: 21 },
  { name: '王五', age: 20 }
];
console.log(groupBy(students, 'age'));
// { '20': [{name: '张三', age: 20}, {name: '王五', age: 20}], '21': [{name: '李四', age: 21}] }

7. 对象属性求和

// 对象数组属性求和
const sumProperty = (arr, prop) => {
  return arr.reduce((acc, cur) => acc + (cur[prop] || 0), 0);
};

// 示例
const products = [
  { name: '苹果', price: 10 },
  { name: '香蕉', price: 5 },
  { name: '橙子', price: 8 }
];
console.log(sumProperty(products, 'price')); // 23

8. 数组转对象

// 数组转对象(以某个属性为key)
const arrayToObject = (arr, key) => {
  return arr.reduce((acc, cur) => {
    acc[cur[key]] = cur;
    return acc;
  }, {});
};

// 示例
const users = [
  { id: 1, name: '张三' },
  { id: 2, name: '李四' }
];
console.log(arrayToObject(users, 'id'));
// { '1': {id: 1, name: '张三'}, '2': {id: 2, name: '李四'} }

9. 管道函数(函数组合)

// 函数组合(从左到右执行)
const pipe = (...fns) => {
  return (initialValue) => {
    return fns.reduce((acc, fn) => fn(acc), initialValue);
  };
};

// 示例
const add5 = x => x + 5;
const multiply3 = x => x * 3;
const subtract2 = x => x - 2;

const calculate = pipe(add5, multiply3, subtract2);
console.log(calculate(10)); // (10 + 5) * 3 - 2 = 43

10. 数组求最大/最小值

// 求最大值
const max = (arr) => {
  return arr.reduce((acc, cur) => Math.max(acc, cur), -Infinity);
};

// 求最小值
const min = (arr) => {
  return arr.reduce((acc, cur) => Math.min(acc, cur), Infinity);
};

// 示例
console.log(max([3, 7, 2, 9, 1])); // 9
console.log(min([3, 7, 2, 9, 1])); // 1

11. 对象合并

// 合并多个对象
const mergeObjects = (...objects) => {
  return objects.reduce((acc, cur) => {
    return { ...acc, ...cur };
  }, {});
};

// 示例
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const obj3 = { d: 5 };
console.log(mergeObjects(obj1, obj2, obj3)); 
// { a: 1, b: 3, c: 4, d: 5 }

12. 数组交集/并集

// 数组交集
const intersection = (arr1, arr2) => {
  return arr1.reduce((acc, cur) => {
    return arr2.includes(cur) ? [...acc, cur] : acc;
  }, []);
};

// 数组并集
const union = (arr1, arr2) => {
  return arr2.reduce((acc, cur) => {
    return acc.includes(cur) ? acc : [...acc, cur];
  }, arr1);
};

// 示例
console.log(intersection([1, 2, 3], [2, 3, 4])); // [2, 3]
console.log(union([1, 2, 3], [2, 3, 4])); // [1, 2, 3, 4]

13. 构建树形结构

// 将扁平数组转换为树形结构
const buildTree = (items, parentId = null) => {
  return items.reduce((acc, item) => {
    if (item.parentId === parentId) {
      const children = buildTree(items, item.id);
      acc.push({
        ...item,
        children: children.length ? children : undefined
      });
    }
    return acc;
  }, []);
};

// 示例
const flatData = [
  { id: 1, name: '根节点', parentId: null },
  { id: 2, name: '子节点1', parentId: 1 },
  { id: 3, name: '子节点2', parentId: 1 },
  { id: 4, name: '孙节点', parentId: 2 }
];
console.log(JSON.stringify(buildTree(flatData), null, 2));

14. 异步串行执行

// 使用 reduce 实现 Promise 串行执行
const runPromisesInSeries = (promises) => {
  return promises.reduce((promiseChain, currentPromise) => {
    return promiseChain.then(chainResults =>
      currentPromise.then(currentResult =>
        [...chainResults, currentResult]
      )
    );
  }, Promise.resolve([]));
};

// 示例
const delay = (ms, value) => new Promise(resolve => 
  setTimeout(() => resolve(value), ms)
);

const promises = [
  () => delay(1000, '第一个'),
  () => delay(500, '第二个'),
  () => delay(800, '第三个')
];

runPromisesInSeries(promises.map(p => p())).then(results => {
  console.log(results); // ['第一个', '第二个', '第三个']
});

使用示例

// 综合示例:处理购物车数据
const cart = [
  { id: 1, name: '商品A', price: 100, quantity: 2 },
  { id: 2, name: '商品B', price: 50, quantity: 3 },
  { id: 3, name: '商品C', price: 80, quantity: 1 }
];

// 1. 计算总价
const totalPrice = cart.reduce((total, item) => {
  return total + item.price * item.quantity;
}, 0);
console.log('总价:', totalPrice); // 430

// 2. 获取所有商品名称
const productNames = cart.reduce((names, item) => {
  return [...names, item.name];
}, []);
console.log('商品列表:', productNames); // ['商品A', '商品B', '商品C']

// 3. 按价格分组
const groupedByPrice = cart.reduce((groups, item) => {
  const priceRange = item.price >= 80 ? '高价' : '低价';
  (groups[priceRange] = groups[priceRange] || []).push(item);
  return groups;
}, {});
console.log('价格分组:', groupedByPrice);

// 4. 构建商品映射表
const productMap = cart.reduce((map, item) => {
  map[item.id] = item;
  return map;
}, {});
console.log('商品映射:', productMap);

关键点

  • 初始值的重要性:合理设置 initialValue 可以避免空数组报错,并确定返回值类型

  • 累加器的灵活性:累加器不仅可以是数字,还可以是对象、数组、字符串等任何类型

  • 不改变原数组:reduce 不会修改原数组,符合函数式编程的不可变性原则

  • 性能考虑:对于简单的求和等操作,传统 for 循环性能更好;reduce 更适合复杂的数据转换

  • 可读性权衡:过度使用 reduce 可能降低代码可读性,简单场景建议使用 map、filter 等方法

  • 链式调用:reduce 可以与其他数组方法链式调用,实现复杂的数据处理流程

  • 空数组处理:没有初始值时,空数组调用 reduce 会报错,务必提供初始值

  • 返回值类型:reduce 的返回值类型完全由累加器决定,可以实现数组到任意类型的转换

  • 函数式编程:reduce 是函数式编程的工具,适合实现管道、组合等高阶操作