reduce 基础·进阶·高阶用法
基础语法
array.reduce(function(total, currentValue, currentIndex, arr), initialValue);参数说明:
total: 必需。累积器,初始值或者上一次回调的返回值currentValue: 必需。当前正在处理的数组元素currentIndex: 可选。当前元素的索引arr: 可选。当前元素所属的数组对象initialValue: 可选。传递给函数的初始值
实现原理
// 手动实现 Array.prototype.reduce
Array.prototype.myReduce = function (callback, initialValue) {
// 1. 检查回调函数是否为函数
if (typeof callback !== "function") {
throw new TypeError("Callback must be a function");
}
// 2. 获取数组长度和起始索引
const array = this;
const length = array.length;
let accumulator;
let startIndex;
// 3. 处理初始值
if (arguments.length >= 2) {
// 有初始值,从索引 0 开始
accumulator = initialValue;
startIndex = 0;
} else {
// 无初始值,检查数组是否为空
if (length === 0) {
throw new TypeError("Reduce of empty array with no initial value");
}
// 使用数组第一个元素作为初始值,从索引 1 开始
accumulator = array[0];
startIndex = 1;
}
// 4. 遍历数组,执行回调函数
for (let i = startIndex; i < length; i++) {
// 跳过稀疏数组的空位
if (i in array) {
accumulator = callback(accumulator, array[i], i, array);
}
}
// 5. 返回累积结果
return accumulator;
};
// 测试自定义实现
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.myReduce((acc, cur) => acc + cur, 0);
console.log(sum); // 15执行流程详解
让我们通过一个具体的例子来理解 reduce 的执行过程:
const arr = [1, 2, 3, 4];
const result = arr.reduce((acc, cur, index) => {
console.log(`第 ${index + 1} 次执行:`);
console.log(` 累积值(acc): ${acc}`);
console.log(` 当前值(cur): ${cur}`);
console.log(` 返回值: ${acc + cur}`);
console.log("---");
return acc + cur;
}, 0);
/*
输出:
第 1 次执行:
累积值(acc): 0
当前值(cur): 1
返回值: 1
---
第 2 次执行:
累积值(acc): 1
当前值(cur): 2
返回值: 3
---
第 3 次执行:
累积值(acc): 3
当前值(cur): 3
返回值: 6
---
第 4 次执行:
累积值(acc): 6
当前值(cur): 4
返回值: 10
---
最终结果: 10
*/关键概念解析
1. 累积器(Accumulator)
// 累积器保存每次回调的返回值
const steps = [];
[1, 2, 3].reduce((acc, cur) => {
const newAcc = acc + cur;
steps.push({ acc, cur, newAcc });
return newAcc;
}, 0);
console.log(steps);
/*
[
{ acc: 0, cur: 1, newAcc: 1 },
{ acc: 1, cur: 2, newAcc: 3 },
{ acc: 3, cur: 3, newAcc: 6 }
]
*/2. 初始值的影响
const arr = [1, 2, 3];
// 有初始值:从第一个元素开始遍历
const withInitial = arr.reduce((acc, cur, index) => {
console.log(`索引 ${index}: acc=${acc}, cur=${cur}`);
return acc + cur;
}, 10);
/*
索引 0: acc=10, cur=1
索引 1: acc=11, cur=2
索引 2: acc=13, cur=3
结果: 16
*/
// 无初始值:从第二个元素开始遍历
const withoutInitial = arr.reduce((acc, cur, index) => {
console.log(`索引 ${index}: acc=${acc}, cur=${cur}`);
return acc + cur;
});
/*
索引 1: acc=1, cur=2
索引 2: acc=3, cur=3
结果: 6
*/边界情况处理
// 1. 空数组处理
try {
[].reduce((acc, cur) => acc + cur); // 抛出错误
} catch (e) {
console.log("空数组必须提供初始值");
}
const emptyResult = [].reduce((acc, cur) => acc + cur, 0); // 返回 0
// 2. 稀疏数组处理
const sparse = [1, , 3, , 5]; // 包含空位的数组
const sparseSum = sparse.reduce((acc, cur, index) => {
console.log(`处理索引 ${index}: ${cur}`);
return acc + cur;
}, 0);
/*
处理索引 0: 1
处理索引 2: 3
处理索引 4: 5
结果: 9 (跳过了空位)
*/
// 3. 单元素数组
const single = [42];
const singleResult = single.reduce((acc, cur) => acc + cur);
console.log(singleResult); // 42 (直接返回该元素,不执行回调)基础用法
实例一:数组求和
const numbers = [1, 2, 3, 4, 5];
// 基础求和
const sum = numbers.reduce((total, current) => total + current, 0);
console.log(sum); // 15
// 不提供初始值(从数组第二个元素开始)
const sum2 = numbers.reduce((total, current) => total + current);
console.log(sum2); // 15实例二:数组求最大值
const numbers = [5, 2, 8, 1, 9, 3];
const max = numbers.reduce((prev, current) => {
return current > prev ? current : prev;
});
console.log(max); // 9实例三:字符串拼接
const words = ["Hello", "World", "JavaScript"];
const sentence = words.reduce((acc, word) => acc + " " + word, "");
console.log(sentence.trim()); // "Hello World JavaScript"进阶用法
实例四:数组扁平化
const nestedArray = [
[1, 2],
[3, 4],
[5, 6],
];
const flattened = nestedArray.reduce((acc, current) => acc.concat(current), []);
console.log(flattened); // [1, 2, 3, 4, 5, 6]
// 处理多层嵌套
const deepNested = [1, [2, [3, 4]], 5];
const flatten = (arr) => {
return arr.reduce((acc, val) => {
return acc.concat(Array.isArray(val) ? flatten(val) : val);
}, []);
};
console.log(flatten(deepNested)); // [1, 2, 3, 4, 5]实例五:统计数组元素出现次数
const fruits = ["apple", "banana", "apple", "orange", "banana", "apple"];
const count = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {});
console.log(count);
// { apple: 3, banana: 2, orange: 1 }实例六:数组分组
const people = [
{ name: "张三", age: 25, department: "技术" },
{ name: "李四", age: 30, department: "市场" },
{ name: "王五", age: 28, department: "技术" },
{ name: "赵六", age: 35, department: "市场" },
];
const groupedByDept = people.reduce((acc, person) => {
const dept = person.department;
if (!acc[dept]) {
acc[dept] = [];
}
acc[dept].push(person);
return acc;
}, {});
console.log(groupedByDept);
/*
{
技术: [
{ name: '张三', age: 25, department: '技术' },
{ name: '王五', age: 28, department: '技术' }
],
市场: [
{ name: '李四', age: 30, department: '市场' },
{ name: '赵六', age: 35, department: '市场' }
]
}
*/实例七:数组去重
const numbers = [1, 2, 2, 3, 4, 4, 5];
const unique = numbers.reduce((acc, current) => {
if (!acc.includes(current)) {
acc.push(current);
}
return acc;
}, []);
console.log(unique); // [1, 2, 3, 4, 5]
// 对象数组去重
const users = [
{ id: 1, name: "张三" },
{ id: 2, name: "李四" },
{ id: 1, name: "张三" },
{ id: 3, name: "王五" },
];
const uniqueUsers = users.reduce((acc, current) => {
const exists = acc.find((user) => user.id === current.id);
if (!exists) {
acc.push(current);
}
return acc;
}, []);
console.log(uniqueUsers);
// [{ id: 1, name: '张三' }, { id: 2, name: '李四' }, { id: 3, name: '王五' }]高阶用法
实例八:函数管道(Pipeline)
// 创建一个函数管道,从左到右执行函数
const pipe = (...functions) => {
return (initialValue) => {
return functions.reduce((acc, fn) => fn(acc), initialValue);
};
};
// 使用示例
const add10 = (x) => x + 10;
const multiply2 = (x) => x * 2;
const subtract5 = (x) => x - 5;
const pipeline = pipe(add10, multiply2, subtract5);
console.log(pipeline(5)); // (5 + 10) * 2 - 5 = 25实例九:实现 Promise 链式调用
const asyncOperations = [
(x) => Promise.resolve(x + 1),
(x) => Promise.resolve(x * 2),
(x) => Promise.resolve(x + 3),
];
const runSequentially = (operations, initialValue) => {
return operations.reduce(
(promiseChain, operation) => promiseChain.then(operation),
Promise.resolve(initialValue)
);
};
// 使用
runSequentially(asyncOperations, 5).then((result) => console.log(result)); // 15: (5 + 1) * 2 + 3实例十:深度合并对象
const deepMerge = (objects) => {
return objects.reduce((merged, obj) => {
Object.keys(obj).forEach((key) => {
if (
obj[key] &&
typeof obj[key] === "object" &&
!Array.isArray(obj[key])
) {
merged[key] = deepMerge([merged[key] || {}, obj[key]]);
} else {
merged[key] = obj[key];
}
});
return merged;
}, {});
};
const obj1 = { a: 1, b: { c: 2, d: 3 } };
const obj2 = { b: { e: 4 }, f: 5 };
const obj3 = { b: { d: 6 }, g: 7 };
const result = deepMerge([obj1, obj2, obj3]);
console.log(result);
// { a: 1, b: { c: 2, d: 6, e: 4 }, f: 5, g: 7 }实例十一:状态管理器(类似 Redux)
// 简单的状态管理实现
const createStore = (reducer, initialState) => {
let state = initialState;
const listeners = [];
return {
getState: () => state,
dispatch: (action) => {
state = reducer(state, action);
listeners.forEach((listener) => listener());
},
subscribe: (listener) => {
listeners.push(listener);
return () => {
const index = listeners.indexOf(listener);
if (index > -1) {
listeners.splice(index, 1);
}
};
},
};
};
// Reducer 使用 reduce 来处理动作队列
const todoReducer = (state = { todos: [] }, action) => {
switch (action.type) {
case "ADD_TODO":
return {
...state,
todos: [...state.todos, action.payload],
};
case "BATCH_ACTIONS":
return action.actions.reduce(todoReducer, state);
default:
return state;
}
};
const store = createStore(todoReducer, { todos: [] });实际应用场景
场景一:Vue 项目中的数据处理
// 在 Vue 组件中处理 API 返回的数据
export default {
data() {
return {
products: [],
categoryStats: {},
};
},
methods: {
processProducts(products) {
// 计算各分类的统计信息
this.categoryStats = products.reduce((stats, product) => {
const category = product.category;
if (!stats[category]) {
stats[category] = {
count: 0,
totalPrice: 0,
avgPrice: 0,
};
}
stats[category].count++;
stats[category].totalPrice += product.price;
stats[category].avgPrice =
stats[category].totalPrice / stats[category].count;
return stats;
}, {});
},
},
};场景二:表单验证
const validateForm = (formData, rules) => {
return Object.keys(rules).reduce((errors, field) => {
const fieldRules = rules[field];
const fieldErrors = fieldRules.reduce((fieldErrors, rule) => {
if (!rule.validator(formData[field])) {
fieldErrors.push(rule.message);
}
return fieldErrors;
}, []);
if (fieldErrors.length > 0) {
errors[field] = fieldErrors;
}
return errors;
}, {});
};
// 使用示例
const formData = { email: "", password: "123" };
const rules = {
email: [
{ validator: (val) => !!val, message: "邮箱不能为空" },
{ validator: (val) => /@/.test(val), message: "邮箱格式不正确" },
],
password: [{ validator: (val) => val.length >= 6, message: "密码至少6位" }],
};
const errors = validateForm(formData, rules);
console.log(errors);性能注意事项
避免在回调中创建新对象:每次迭代都创建新对象会影响性能
合理使用初始值:提供初始值可以避免类型错误
考虑使用其他方法:某些场景下
map、filter等方法可能更直观大数组处理:对于超大数组,考虑分批处理或使用 Web Workers
总结
reduce 是一个功能强大的数组方法,它能够:
将数组转换为单一值
实现复杂的数据转换
替代多个链式调用
实现函数式编程模式
处理异步操作序列