跳到主要内容

手写 map 和 filter

问题

实现数组的 mapfilter 方法。

答案

mapfilter 是最常用的数组高阶函数,也是面试高频手写题。


手写 map

基础实现

interface Array<T> {
myMap<U>(
callback: (value: T, index: number, array: T[]) => U,
thisArg?: unknown
): U[];
}

Array.prototype.myMap = function <T, U>(
this: T[],
callback: (value: T, index: number, array: T[]) => U,
thisArg?: unknown
): U[] {
if (typeof callback !== 'function') {
throw new TypeError(`${callback} is not a function`);
}

const result: U[] = [];

for (let i = 0; i < this.length; i++) {
// 稀疏数组:跳过空位
if (i in this) {
result[i] = callback.call(thisArg, this[i], i, this);
}
}

return result;
};

// 测试
console.log([1, 2, 3].myMap((x) => x * 2)); // [2, 4, 6]

console.log(
[1, 2, 3].myMap(function (this: { multiplier: number }, x) {
return x * this.multiplier;
}, { multiplier: 10 })
); // [10, 20, 30]

独立函数版本

function map<T, U>(
array: T[],
callback: (value: T, index: number, array: T[]) => U,
thisArg?: unknown
): U[] {
if (!Array.isArray(array)) {
throw new TypeError('First argument must be an array');
}

const result: U[] = 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

基础实现

interface Array<T> {
myFilter(
callback: (value: T, index: number, array: T[]) => boolean,
thisArg?: unknown
): T[];
}

Array.prototype.myFilter = function <T>(
this: T[],
callback: (value: T, index: number, array: T[]) => boolean,
thisArg?: unknown
): T[] {
if (typeof callback !== 'function') {
throw new TypeError(`${callback} is not a function`);
}

const result: T[] = [];

for (let i = 0; i < this.length; i++) {
if (i in this) {
if (callback.call(thisArg, this[i], i, this)) {
result.push(this[i]);
}
}
}

return result;
};

// 测试
console.log([1, 2, 3, 4, 5].myFilter((x) => x > 2)); // [3, 4, 5]

console.log(
[1, 2, 3, 4, 5].myFilter(function (this: { min: number }, x) {
return x >= this.min;
}, { min: 3 })
); // [3, 4, 5]

独立函数版本

function filter<T>(
array: T[],
callback: (value: T, index: number, array: T[]) => boolean,
thisArg?: unknown
): T[] {
const result: T[] = [];

for (let i = 0; i < array.length; i++) {
if (i in array) {
if (callback.call(thisArg, array[i], i, array)) {
result.push(array[i]);
}
}
}

return result;
}

其他常用方法

forEach

Array.prototype.myForEach = function <T>(
this: T[],
callback: (value: T, index: number, array: T[]) => void,
thisArg?: unknown
): void {
for (let i = 0; i < this.length; i++) {
if (i in this) {
callback.call(thisArg, this[i], i, this);
}
}
};

some

Array.prototype.mySome = function <T>(
this: T[],
callback: (value: T, index: number, array: T[]) => boolean,
thisArg?: unknown
): boolean {
for (let i = 0; i < this.length; i++) {
if (i in this) {
if (callback.call(thisArg, this[i], i, this)) {
return true;
}
}
}
return false;
};

every

Array.prototype.myEvery = function <T>(
this: T[],
callback: (value: T, index: number, array: T[]) => boolean,
thisArg?: unknown
): boolean {
for (let i = 0; i < this.length; i++) {
if (i in this) {
if (!callback.call(thisArg, this[i], i, this)) {
return false;
}
}
}
return true;
};

find / findIndex

Array.prototype.myFind = function <T>(
this: T[],
callback: (value: T, index: number, array: T[]) => boolean,
thisArg?: unknown
): T | undefined {
for (let i = 0; i < this.length; i++) {
if (i in this) {
if (callback.call(thisArg, this[i], i, this)) {
return this[i];
}
}
}
return undefined;
};

Array.prototype.myFindIndex = function <T>(
this: T[],
callback: (value: T, index: number, array: T[]) => boolean,
thisArg?: unknown
): number {
for (let i = 0; i < this.length; i++) {
if (i in this) {
if (callback.call(thisArg, this[i], i, this)) {
return i;
}
}
}
return -1;
};

includes

Array.prototype.myIncludes = function <T>(
this: T[],
searchElement: T,
fromIndex = 0
): boolean {
const len = this.length;
let start = fromIndex >= 0 ? fromIndex : Math.max(len + fromIndex, 0);

for (let i = start; i < len; i++) {
// 使用 SameValueZero 算法
if (this[i] === searchElement || (Number.isNaN(this[i]) && Number.isNaN(searchElement))) {
return true;
}
}
return false;
};

// 测试
console.log([1, 2, NaN].myIncludes(NaN)); // true

indexOf

Array.prototype.myIndexOf = function <T>(
this: T[],
searchElement: T,
fromIndex = 0
): number {
const len = this.length;
let start = fromIndex >= 0 ? fromIndex : Math.max(len + fromIndex, 0);

for (let i = start; i < len; i++) {
if (i in this && this[i] === searchElement) {
return i;
}
}
return -1;
};

方法对比

方法返回值是否改变原数组是否可中断
map新数组
filter新数组
forEachundefined
someboolean
everyboolean
find元素/undefined
reduce任意值

链式调用优化

// 问题:多次遍历
const result = [1, 2, 3, 4, 5]
.map((x) => x * 2) // 遍历 1
.filter((x) => x > 5) // 遍历 2
.map((x) => x + 1); // 遍历 3

// 优化:合并操作
function chainOptimize<T, U>(
array: T[],
operations: Array<{
type: 'map' | 'filter';
fn: (value: unknown) => unknown;
}>
): U[] {
const result: U[] = [];

outer: for (let i = 0; i < array.length; i++) {
let value: unknown = array[i];

for (const op of operations) {
if (op.type === 'map') {
value = op.fn(value);
} else if (op.type === 'filter') {
if (!op.fn(value)) {
continue outer;
}
}
}

result.push(value as U);
}

return result;
}

// 使用
const optimized = chainOptimize<number, number>(
[1, 2, 3, 4, 5],
[
{ type: 'map', fn: (x) => (x as number) * 2 },
{ type: 'filter', fn: (x) => (x as number) > 5 },
{ type: 'map', fn: (x) => (x as number) + 1 },
]
);
// 只遍历一次

常见面试问题

Q1: map 和 forEach 的区别?

答案

// map:返回新数组
const doubled = [1, 2, 3].map((x) => x * 2); // [2, 4, 6]

// forEach:返回 undefined,用于副作用
[1, 2, 3].forEach((x) => console.log(x)); // undefined
区别mapforEach
返回值新数组undefined
用途转换数据执行副作用
链式调用支持不支持

Q2: 稀疏数组如何处理?

答案

const sparse = [1, , 3]; // 稀疏数组

// 原生方法会跳过空位
sparse.map((x, i) => {
console.log(i); // 0, 2(跳过了 1)
return x;
});

// 手写时使用 `in` 操作符判断
for (let i = 0; i < array.length; i++) {
if (i in array) {
// 只处理存在的元素
}
}

Q3: thisArg 参数的作用?

答案

const obj = {
multiplier: 2,
multiply(arr: number[]) {
return arr.map(function (this: typeof obj, x) {
return x * this.multiplier;
}, this); // 传入 thisArg
},
};

console.log(obj.multiply([1, 2, 3])); // [2, 4, 6]

// 箭头函数不需要 thisArg
const obj2 = {
multiplier: 2,
multiply(arr: number[]) {
return arr.map((x) => x * this.multiplier);
},
};

相关链接