手写实现 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)
-
累加器更新:每次迭代都要用回调函数的返回值更新累加器
-
返回值:最终返回累加器的值,可以是任意类型(数字、字符串、对象、数组等)
目录