JavaScript原生数组方法无法完全等价实现,但可模拟核心逻辑:map需用for循环配合hasOwnProperty判断稀疏数组空位,正确绑定this,返回等长新数组且保留空位结构。

JavaScript 原生数组方法不能直接“实现”为完全等价的用户代码,但可以手动模拟其核心行为逻辑——关键在于理解它们的执行契约(如遍历顺序、this 绑定、返回值规则)和边界条件(空数组、稀疏数组、抛错处理)。
map 的手动模拟要注意回调函数的 this 和稀疏数组处理
原生 map 会跳过空位(sparse array),但保留索引位置;它支持传入第二个参数作为回调的 this 值;不修改原数组。
- 必须用
for循环 +hasOwnProperty或in判断索引是否存在,不能只用for...of(会忽略空位) - 需显式调用
callback.call(thisArg, item, index, array)保证this正确 - 返回新数组长度必须与原数组一致,空位对应位置也应是空位(可用
new Array(len)+ 属性赋值)
function myMap(array, callback, thisArg) {
const result = new Array(array.length);
for (let i = 0; i < array.length; i++) {
if (i in array) {
result[i] = callback.call(thisArg, array[i], i, array);
}
}
return result;
}
filter 必须严格区分“falsy 返回值”和“未定义返回值”
原生 filter 只根据回调返回值的布尔转换结果决定是否保留元素,不是检查是否为 true 字面量;且不跳过空位,而是对每个存在的索引调用回调。
- 不能用
array[i] && callback(...),因为0、''、null等 falsy 值可能被错误过滤 - 必须用
Boolean(callback(...))或!!callback(...)显式转布尔 - 返回数组长度由实际保留元素数决定,不是原数组长度
function myFilter(array, callback, thisArg) {
const result = [];
for (let i = 0; i < array.length; i++) {
if (i in array && Boolean(callback.call(thisArg, array[i], i, array))) {
result.push(array[i]);
}
}
return result;
}
reduce 的初始值逻辑和 TypeError 边界最容易出错
原生 reduce 在无初始值且数组为空时抛 TypeError: Reduce of empty array with no initial value;有初始值时,第一次调用回调的 accumulator 就是该值,currentValue 是索引 0 元素;无初始值时,accumulator 是索引 0,currentValue 是索引 1 —— 这个偏移容易写反。
立即学习“Java免费学习笔记(深入)”;
- 必须先判断
initialValue === undefined,再检查数组长度是否 ≥ 1,否则直接 throw - 循环起始索引取决于是否有
initialValue:有则从 0 开始,无则从 1 开始 - 回调调用必须用
callback.call(thisArg, accumulator, currentValue, index, array),顺序不能错
function myReduce(array, callback, initialValue) {
if (array.length === 0 && initialValue === undefined) {
throw new TypeError('Reduce of empty array with no initial value');
}
let accumulator = initialValue !== undefined ? initialValue : array[0];
let startIndex = initialValue !== undefined ? 0 : 1;
for (let i = startIndex; i < array.length; i++) {
if (i in array) {
accumulator = callback.call(undefined, accumulator, array[i], i, array);
}
}
return accumulator;
}
真正难的不是循环本身,而是对 ECMA 规范中“抽象操作”(如 IsCallable、ToObject、HasProperty)的忠实还原——比如 callback 必须是可调用对象,否则原生方法会立即 throw,而手动实现常直接忽略这层校验。生产环境别重写,调试或学习时才需要抠这些细节。











