reduce 就是通过一个累积器 ,把数组中的所有元素“遍历-回调-累积”,最终得到一个单一的结果。
主干
Array.prototype.myReduce = function(callback, initialValue) {
// 1. 初始化累积器
let accumulator = initialValue;
// 2. 遍历数组
for (let i = 0; i < this.length; i++) {
// 3. 调用回调函数,更新累积器
accumulator = callback(accumulator, this[i], i, this);
}
// 4. 返回最终结果
return accumulator;
};
树枝
防御性编程
问题:主干版本没有进行任何参数校验,如果传入非函数或 this 为 null 会出错。
添加的树枝:在函数开头增加参数校验。
Array.prototype.myReduce = function(callback, initialValue) {
// 【新增】防御性编程:检查 this 和 callback
if (this == null) throw new TypeError('this is null or undefined');
if (typeof callback !== 'function') throw new TypeError('callback must be a function');
let accumulator = initialValue;
for (let i = 0; i < this.length; i++) {
accumulator = callback(accumulator, this[i], i, this);
}
return accumulator;
};
处理类数组对象
问题:主干版本直接使用 this,但在类数组对象(如 arguments、字符串)上调用会出错。
添加的树枝:将 this 转换为对象,并安全地获取其长度。
Array.prototype.myReduce = function(callback, initialValue) {
if (this == null) throw new TypeError('this is null or undefined');
if (typeof callback !== 'function') throw new TypeError('callback must be a function');
// 【新增】处理类数组对象
const O = Object(this); // 确保this总是一个对象
const len = O.length >>> 0; // 确保长度是非负整数
let accumulator = initialValue;
// 【修改】遍历 O 而不是 this
for (let i = 0; i < len; i++) {
accumulator = callback(accumulator, O[i], i, O);
}
return accumulator;
};
正确处理初始值逻辑
问题:主干版本对 initialValue 的处理不完善,没有考虑无初始值时的情况,这会导致错误。
添加的树枝:重写初始化逻辑,根据有无 initialValue 设置不同的起始点和累积器初始值。
Array.prototype.myReduce = function(callback, initialValue) {
if (this == null) throw new TypeError('this is null or undefined');
if (typeof callback !== 'function') throw new TypeError('callback must be a function');
const O = Object(this);
const len = O.length >>> 0;
// 【新增】处理初始值的复杂逻辑
let k = 0; // 起始索引
let accumulator; // 累积器
if (arguments.length >= 2) { // 有初始值
accumulator = initialValue;
} else { // 无初始值
// 找到第一个存在的元素作为初始值
while (k < len && !(k in O)) k++;
if (k >= len) throw new TypeError('Reduce of empty array with no initial value');
accumulator = O[k++];
}
// 【修改】从 k 开始遍历
for (; k < len; k++) {
accumulator = callback(accumulator, O[k], k, O);
}
return accumulator;
};
处理稀疏数组
问题:即使到了上一步,for 循环仍然会把空位(empty slot)当作 undefined 来处理,不符合原生 reduce 行为。
添加的树枝:在循环内部增加判断,跳过稀疏数组的空位。
Array.prototype.myReduce = function(callback, initialValue) {
if (this == null) throw new TypeError('this is null or undefined');
if (typeof callback !== 'function') throw new TypeError('callback must be a function');
const O = Object(this);
const len = O.length >>> 0;
let k = 0;
let accumulator;
if (arguments.length >= 2) {
accumulator = initialValue;
} else {
while (k < len && !(k in O)) k++;
if (k >= len) throw new TypeError('Reduce of empty array with no initial value');
accumulator = O[k++];
}
// 【新增】处理稀疏数组:跳过空位
while (k < len) {
if (k in O) { // 关键:检查索引是否存在
accumulator = callback(accumulator, O[k], k, O);
}
k++;
}
return accumulator;
};
测试
// 1. 基本功能测试:数组求和(有初始值)
console.log('--- 1. 基本求和(有初始值) ---');
const sum1 = [1, 2, 3, 4].myReduce((acc, cur) => acc + cur, 10);
console.log('myReduce 结果:', sum1); // 期望: 20
console.log('原生 reduce 结果:', [1, 2, 3, 4].reduce((acc, cur) => acc + cur, 10)); // 期望: 20
console.log('---------------------------\n');
// 2. 基本功能测试:数组求和(无初始值)
console.log('--- 2. 基本求和(无初始值) ---');
const sum2 = [1, 2, 3, 4].myReduce((acc, cur) => acc + cur);
console.log('myReduce 结果:', sum2); // 期望: 10
console.log('原生 reduce 结果:', [1, 2, 3, 4].reduce((acc, cur) => acc + cur)); // 期望: 10
console.log('---------------------------\n');
// 3. 边界情况测试:空数组(有初始值)
console.log('--- 3. 空数组(有初始值) ---');
const emptyWithInitial = [].myReduce((acc, cur) => acc + cur, 'start');
console.log('myReduce 结果:', emptyWithInitial); // 期望: 'start'
console.log('原生 reduce 结果:', [].reduce((acc, cur) => acc + cur, 'start')); // 期望: 'start'
console.log('---------------------------\n');
// 4. 边界情况测试:空数组(无初始值)
console.log('--- 4. 空数组(无初始值) ---');
try {
[].myReduce((acc, cur) => acc + cur);
} catch (e) {
console.log('myReduce 捕获到错误:', e.message); // 期望: 'Reduce of empty array with no initial value'
}
try {
[].reduce((acc, cur) => acc + cur);
} catch (e) {
console.log('原生 reduce 捕获到错误:', e.message); // 期望: 'Reduce of empty array with no initial value'
}
console.log('---------------------------\n');
// 5. 稀疏数组测试:跳过空位
console.log('--- 5. 稀疏数组测试 ---');
const sparse = [1, , 3, , 5];
const sparseSum = sparse.myReduce((acc, cur) => acc + cur, 0);
console.log('稀疏数组:', sparse);
console.log('myReduce 结果:', sparseSum); // 期望: 9 (1+3+5)
console.log('原生 reduce 结果:', sparse.reduce((acc, cur) => acc + cur, 0)); // 期望: 9
console.log('---------------------------\n');
// 6. 类数组对象测试:在 arguments 上调用
console.log('--- 6. 类数组对象测试 ---');
function testArguments() {
const argsSum = Array.prototype.myReduce.call(arguments, (acc, cur) => acc + cur, 0);
console.log('arguments 对象:', arguments);
console.log('myReduce 在 arguments 上的结果:', argsSum); // 期望: 15 (5+10)
console.log('原生 reduce 在 arguments 上的结果:', Array.prototype.reduce.call(arguments, (acc, cur) => acc + cur, 0)); // 期望: 15
}
testArguments(5, 10);
console.log('---------------------------\n');
// 7. 类数组对象测试:在字符串上调用
console.log('--- 7. 字符串测试 ---');
const reversedStr = Array.prototype.myReduce.call('hello', (acc, cur) => cur + acc, '');
console.log('myReduce 在字符串上的结果:', reversedStr); // 期望: 'olleh'
console.log('原生 reduce 在字符串上的结果:', Array.prototype.reduce.call('hello', (acc, cur) => cur + acc, '')); // 期望: 'olleh'
console.log('---------------------------\n');
// 8. 防御性编程测试:callback 不是函数
console.log('--- 8. callback 不是函数 ---');
try {
[1, 2, 3].myReduce('not a function');
} catch (e) {
console.log('myReduce 捕获到错误:', e.message); // 期望: 'callback must be a function'
}
try {
[1, 2, 3].reduce('not a function');
} catch (e) {
console.log('原生 reduce 捕获到错误:', e.message); // 期望: 'callback must be a function'
}
console.log('---------------------------\n');