Search K
Appearance
Appearance
📊 SEO元描述:2024年最新JavaScript手写数组方法教程,详解map、filter、reduce、forEach等方法的底层实现。包含完整代码示例和面试重点,适合前端开发者深入理解JavaScript数组方法原理。
核心关键词:JavaScript手写数组方法2024、手写map filter reduce、JavaScript数组方法实现、前端面试手写代码
长尾关键词:JavaScript怎么手写map方法、手写filter方法实现、JavaScript reduce方法原理、前端面试手写题
通过本节JavaScript手写实现数组方法,你将系统性掌握:
**map方法的核心原理是什么?**map方法通过遍历数组,对每个元素应用回调函数,并将结果收集到新数组中返回。手写map方法需要处理回调函数、this绑定、稀疏数组等多个细节。
💡 学习建议:理解map方法的实现原理有助于更好地使用和优化数组操作
手写map方法需要考虑多个实现细节:
// 🎉 手写map方法的完整实现
console.log('=== 基础版本的map实现 ===');
// 最简单的map实现
Array.prototype.myMapBasic = function(callback) {
const result = [];
for (let i = 0; i < this.length; i++) {
result.push(callback(this[i], i, this));
}
return result;
};
// 测试基础版本
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.myMapBasic(x => x * 2);
console.log('基础map结果:', doubled); // [2, 4, 6, 8, 10]
console.log('=== 完整版本的map实现 ===');
// 完整的map实现(符合ES规范)
Array.prototype.myMap = function(callback, thisArg) {
// 1. 类型检查
if (this == null) {
throw new TypeError('Array.prototype.myMap called on null or undefined');
}
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
// 2. 转换为对象
const O = Object(this);
// 3. 获取length属性
const len = parseInt(O.length) || 0;
// 4. 创建新数组
const A = new Array(len);
// 5. 遍历数组
for (let k = 0; k < len; k++) {
// 检查属性是否存在(处理稀疏数组)
if (k in O) {
// 调用回调函数
const kValue = O[k];
const mappedValue = callback.call(thisArg, kValue, k, O);
A[k] = mappedValue;
}
// 如果k不在O中,A[k]保持为empty(稀疏数组处理)
}
return A;
};
// 测试完整版本
console.log('=== 测试完整map实现 ===');
// 基本功能测试
const testArray = [1, 2, 3, 4, 5];
const mapResult = testArray.myMap((value, index, array) => {
console.log(`处理: value=${value}, index=${index}, arrayLength=${array.length}`);
return value * value;
});
console.log('完整map结果:', mapResult);
// thisArg测试
const multiplier = {
factor: 10,
multiply: function(value) {
return value * this.factor;
}
};
const thisArgResult = testArray.myMap(function(value) {
return this.multiply(value);
}, multiplier);
console.log('thisArg测试结果:', thisArgResult);
// 稀疏数组测试
const sparseArray = [1, , , 4, 5]; // 包含空位
console.log('原稀疏数组:', sparseArray);
const sparseResult = sparseArray.myMap(x => x * 2);
console.log('稀疏数组map结果:', sparseResult);
console.log('结果长度:', sparseResult.length);
// 对比原生map
const nativeResult = sparseArray.map(x => x * 2);
console.log('原生map结果:', nativeResult);
// 错误处理测试
try {
[1, 2, 3].myMap('not a function');
} catch (error) {
console.log('错误处理测试:', error.message);
}
console.log('=== 性能对比测试 ===');
function performanceTest() {
const largeArray = Array.from({ length: 100000 }, (_, i) => i);
console.time('原生map');
const nativeResult = largeArray.map(x => x * 2);
console.timeEnd('原生map');
console.time('手写map');
const customResult = largeArray.myMap(x => x * 2);
console.timeEnd('手写map');
console.log('结果一致性:', nativeResult.length === customResult.length);
}
// performanceTest();
// 高级特性测试
console.log('=== 高级特性测试 ===');
// 类数组对象测试
const arrayLike = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
const arrayLikeResult = Array.prototype.myMap.call(arrayLike, (value, index) => {
return `${index}: ${value.toUpperCase()}`;
});
console.log('类数组对象测试:', arrayLikeResult);
// 字符串测试
const stringResult = Array.prototype.myMap.call('hello', (char, index) => {
return `${index}-${char}`;
});
console.log('字符串测试:', stringResult);// 🎉 map方法的变体实现
console.log('=== map方法的变体实现 ===');
// 异步map实现
Array.prototype.mapAsync = async function(asyncCallback, thisArg) {
if (this == null) {
throw new TypeError('Array.prototype.mapAsync called on null or undefined');
}
if (typeof asyncCallback !== 'function') {
throw new TypeError(asyncCallback + ' is not a function');
}
const O = Object(this);
const len = parseInt(O.length) || 0;
const promises = [];
for (let k = 0; k < len; k++) {
if (k in O) {
const promise = Promise.resolve(asyncCallback.call(thisArg, O[k], k, O));
promises.push(promise);
} else {
promises.push(Promise.resolve(undefined));
}
}
return Promise.all(promises);
};
// 测试异步map
async function testAsyncMap() {
const asyncDouble = async (x) => {
await new Promise(resolve => setTimeout(resolve, 10));
return x * 2;
};
const result = await [1, 2, 3, 4, 5].mapAsync(asyncDouble);
console.log('异步map结果:', result);
}
// testAsyncMap();
// 带索引过滤的map
Array.prototype.mapWithFilter = function(callback, filterCallback, thisArg) {
if (this == null) {
throw new TypeError('Array.prototype.mapWithFilter called on null or undefined');
}
const O = Object(this);
const len = parseInt(O.length) || 0;
const result = [];
for (let k = 0; k < len; k++) {
if (k in O) {
const kValue = O[k];
if (!filterCallback || filterCallback.call(thisArg, kValue, k, O)) {
const mappedValue = callback.call(thisArg, kValue, k, O);
result.push(mappedValue);
}
}
}
return result;
};
// 测试带过滤的map
const filterMapResult = [1, 2, 3, 4, 5, 6].mapWithFilter(
x => x * x, // map函数
x => x % 2 === 0 // filter函数
);
console.log('带过滤的map结果:', filterMapResult); // [4, 16, 36]
// 链式map实现
Array.prototype.chainMap = function(...callbacks) {
return callbacks.reduce((acc, callback) => {
return acc.myMap(callback);
}, this);
};
// 测试链式map
const chainResult = [1, 2, 3, 4, 5].chainMap(
x => x + 1, // 加1
x => x * 2, // 乘2
x => x - 1 // 减1
);
console.log('链式map结果:', chainResult); // [3, 5, 7, 9, 11]**filter方法的工作原理是什么?**filter方法通过遍历数组,对每个元素应用测试函数,只保留返回true的元素到新数组中。手写filter方法需要正确处理布尔值转换和稀疏数组。
// 🎉 手写filter方法的完整实现
console.log('=== 基础版本的filter实现 ===');
// 最简单的filter实现
Array.prototype.myFilterBasic = function(callback) {
const result = [];
for (let i = 0; i < this.length; i++) {
if (callback(this[i], i, this)) {
result.push(this[i]);
}
}
return result;
};
// 测试基础版本
const numbers = [1, 2, 3, 4, 5, 6];
const evens = numbers.myFilterBasic(x => x % 2 === 0);
console.log('基础filter结果:', evens); // [2, 4, 6]
console.log('=== 完整版本的filter实现 ===');
// 完整的filter实现(符合ES规范)
Array.prototype.myFilter = function(callback, thisArg) {
// 1. 类型检查
if (this == null) {
throw new TypeError('Array.prototype.myFilter called on null or undefined');
}
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
// 2. 转换为对象
const O = Object(this);
// 3. 获取length属性
const len = parseInt(O.length) || 0;
// 4. 创建结果数组
const A = [];
// 5. 遍历数组
for (let k = 0; k < len; k++) {
// 检查属性是否存在(处理稀疏数组)
if (k in O) {
const kValue = O[k];
// 调用回调函数并转换为布尔值
const selected = Boolean(callback.call(thisArg, kValue, k, O));
if (selected) {
A.push(kValue);
}
}
}
return A;
};
// 测试完整版本
console.log('=== 测试完整filter实现 ===');
// 基本功能测试
const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const filterResult = testArray.myFilter((value, index, array) => {
console.log(`测试: value=${value}, index=${index}`);
return value > 5;
});
console.log('完整filter结果:', filterResult);
// thisArg测试
const filterConfig = {
threshold: 3,
isAboveThreshold: function(value) {
return value > this.threshold;
}
};
const thisArgResult = testArray.myFilter(function(value) {
return this.isAboveThreshold(value);
}, filterConfig);
console.log('thisArg测试结果:', thisArgResult);
// 稀疏数组测试
const sparseArray = [1, , 3, , 5, 6];
console.log('原稀疏数组:', sparseArray);
const sparseResult = sparseArray.myFilter(x => x > 2);
console.log('稀疏数组filter结果:', sparseResult);
// 布尔值转换测试
const booleanTest = [0, 1, '', 'hello', null, undefined, false, true, NaN, 42];
const truthyValues = booleanTest.myFilter(x => x); // 测试真值
console.log('真值过滤结果:', truthyValues);
// 复杂对象过滤
const users = [
{ name: 'Alice', age: 25, active: true },
{ name: 'Bob', age: 30, active: false },
{ name: 'Charlie', age: 35, active: true },
{ name: 'Diana', age: 28, active: false }
];
const activeUsers = users.myFilter(user => user.active);
const youngUsers = users.myFilter(user => user.age < 30);
console.log('活跃用户:', activeUsers);
console.log('年轻用户:', youngUsers);
console.log('=== filter方法的变体实现 ===');
// 带计数的filter
Array.prototype.filterWithCount = function(callback, thisArg) {
if (this == null) {
throw new TypeError('Array.prototype.filterWithCount called on null or undefined');
}
const O = Object(this);
const len = parseInt(O.length) || 0;
const result = [];
let count = 0;
for (let k = 0; k < len; k++) {
if (k in O) {
const kValue = O[k];
if (callback.call(thisArg, kValue, k, O)) {
result.push(kValue);
count++;
}
}
}
return { filtered: result, count };
};
// 测试带计数的filter
const countResult = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].filterWithCount(x => x % 2 === 0);
console.log('带计数filter结果:', countResult);
// 多条件filter
Array.prototype.filterMultiple = function(...conditions) {
return this.myFilter(item => {
return conditions.every(condition => condition(item));
});
};
// 测试多条件filter
const multiResult = users.filterMultiple(
user => user.age > 25,
user => user.active === true
);
console.log('多条件filter结果:', multiResult);
// 分组filter
Array.prototype.filterGroup = function(callback, thisArg) {
if (this == null) {
throw new TypeError('Array.prototype.filterGroup called on null or undefined');
}
const O = Object(this);
const len = parseInt(O.length) || 0;
const passed = [];
const failed = [];
for (let k = 0; k < len; k++) {
if (k in O) {
const kValue = O[k];
if (callback.call(thisArg, kValue, k, O)) {
passed.push(kValue);
} else {
failed.push(kValue);
}
}
}
return { passed, failed };
};
// 测试分组filter
const groupResult = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].filterGroup(x => x % 2 === 0);
console.log('分组filter结果:', groupResult);**reduce方法的核心机制是什么?**reduce方法通过累加器函数逐步处理数组元素,将数组归纳为单个值。手写reduce方法是最复杂的数组方法实现,需要处理初始值、空数组、稀疏数组等多种情况。
// 🎉 手写reduce方法的完整实现
console.log('=== 基础版本的reduce实现 ===');
// 最简单的reduce实现
Array.prototype.myReduceBasic = function(callback, initialValue) {
let accumulator = initialValue;
let startIndex = 0;
// 如果没有初始值,使用第一个元素作为初始值
if (initialValue === undefined) {
accumulator = this[0];
startIndex = 1;
}
for (let i = startIndex; i < this.length; i++) {
accumulator = callback(accumulator, this[i], i, this);
}
return accumulator;
};
// 测试基础版本
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.myReduceBasic((acc, curr) => acc + curr, 0);
const product = numbers.myReduceBasic((acc, curr) => acc * curr);
console.log('基础reduce求和:', sum); // 15
console.log('基础reduce求积:', product); // 120
console.log('=== 完整版本的reduce实现 ===');
// 完整的reduce实现(符合ES规范)
Array.prototype.myReduce = function(callback, initialValue) {
// 1. 类型检查
if (this == null) {
throw new TypeError('Array.prototype.myReduce called on null or undefined');
}
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
// 2. 转换为对象
const O = Object(this);
// 3. 获取length属性
const len = parseInt(O.length) || 0;
// 4. 处理空数组情况
if (len === 0 && arguments.length < 2) {
throw new TypeError('Reduce of empty array with no initial value');
}
// 5. 初始化累加器和起始索引
let k = 0;
let accumulator;
if (arguments.length >= 2) {
// 有初始值
accumulator = initialValue;
} else {
// 没有初始值,找到第一个存在的元素
let kPresent = false;
while (k < len && !kPresent) {
if (k in O) {
accumulator = O[k];
kPresent = true;
}
k++;
}
if (!kPresent) {
throw new TypeError('Reduce of empty array with no initial value');
}
}
// 6. 遍历数组进行归纳
while (k < len) {
if (k in O) {
accumulator = callback(accumulator, O[k], k, O);
}
k++;
}
return accumulator;
};
// 测试完整版本
console.log('=== 测试完整reduce实现 ===');
// 基本功能测试
const testArray = [1, 2, 3, 4, 5];
const reduceSum = testArray.myReduce((acc, curr, index, array) => {
console.log(`累加: acc=${acc}, curr=${curr}, index=${index}`);
return acc + curr;
}, 0);
console.log('完整reduce求和:', reduceSum);
// 无初始值测试
const noInitialSum = testArray.myReduce((acc, curr) => acc + curr);
console.log('无初始值求和:', noInitialSum);
// 稀疏数组测试
const sparseArray = [1, , , 4, 5];
console.log('原稀疏数组:', sparseArray);
const sparseSum = sparseArray.myReduce((acc, curr) => {
console.log(`稀疏数组累加: acc=${acc}, curr=${curr}`);
return acc + curr;
}, 0);
console.log('稀疏数组求和:', sparseSum);
// 空数组测试
try {
[].myReduce((acc, curr) => acc + curr);
} catch (error) {
console.log('空数组错误:', error.message);
}
console.log('空数组有初始值:', [].myReduce((acc, curr) => acc + curr, 0));
// 复杂对象归纳
const transactions = [
{ type: 'income', amount: 1000 },
{ type: 'expense', amount: 200 },
{ type: 'income', amount: 500 },
{ type: 'expense', amount: 300 }
];
const financial = transactions.myReduce((acc, transaction) => {
if (transaction.type === 'income') {
acc.income += transaction.amount;
acc.total += transaction.amount;
} else {
acc.expense += transaction.amount;
acc.total -= transaction.amount;
}
return acc;
}, { income: 0, expense: 0, total: 0 });
console.log('财务归纳结果:', financial);
// 数组扁平化
const nestedArray = [[1, 2], [3, 4], [5, 6]];
const flattened = nestedArray.myReduce((acc, curr) => acc.concat(curr), []);
console.log('数组扁平化:', flattened);
// 对象数组转换为对象
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
];
const usersById = users.myReduce((acc, user) => {
acc[user.id] = user;
return acc;
}, {});
console.log('用户索引:', usersById);
console.log('=== reduce方法的变体实现 ===');
// reduceRight实现
Array.prototype.myReduceRight = function(callback, initialValue) {
if (this == null) {
throw new TypeError('Array.prototype.myReduceRight called on null or undefined');
}
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
const O = Object(this);
const len = parseInt(O.length) || 0;
if (len === 0 && arguments.length < 2) {
throw new TypeError('Reduce of empty array with no initial value');
}
let k = len - 1;
let accumulator;
if (arguments.length >= 2) {
accumulator = initialValue;
} else {
let kPresent = false;
while (k >= 0 && !kPresent) {
if (k in O) {
accumulator = O[k];
kPresent = true;
}
k--;
}
if (!kPresent) {
throw new TypeError('Reduce of empty array with no initial value');
}
}
while (k >= 0) {
if (k in O) {
accumulator = callback(accumulator, O[k], k, O);
}
k--;
}
return accumulator;
};
// 测试reduceRight
const rightResult = [1, 2, 3, 4, 5].myReduceRight((acc, curr) => {
console.log(`从右累加: acc=${acc}, curr=${curr}`);
return acc + curr;
}, 0);
console.log('从右reduce结果:', rightResult);
// 异步reduce实现
Array.prototype.reduceAsync = async function(asyncCallback, initialValue) {
if (this == null) {
throw new TypeError('Array.prototype.reduceAsync called on null or undefined');
}
if (typeof asyncCallback !== 'function') {
throw new TypeError(asyncCallback + ' is not a function');
}
const O = Object(this);
const len = parseInt(O.length) || 0;
if (len === 0 && arguments.length < 2) {
throw new TypeError('Reduce of empty array with no initial value');
}
let k = 0;
let accumulator;
if (arguments.length >= 2) {
accumulator = initialValue;
} else {
let kPresent = false;
while (k < len && !kPresent) {
if (k in O) {
accumulator = O[k];
kPresent = true;
}
k++;
}
if (!kPresent) {
throw new TypeError('Reduce of empty array with no initial value');
}
}
while (k < len) {
if (k in O) {
accumulator = await asyncCallback(accumulator, O[k], k, O);
}
k++;
}
return accumulator;
};
// 测试异步reduce
async function testAsyncReduce() {
const asyncSum = await [1, 2, 3, 4, 5].reduceAsync(async (acc, curr) => {
await new Promise(resolve => setTimeout(resolve, 10));
return acc + curr;
}, 0);
console.log('异步reduce结果:', asyncSum);
}
// testAsyncReduce();// 🎉 其他重要数组方法的手写实现
console.log('=== 手写forEach方法 ===');
Array.prototype.myForEach = function(callback, thisArg) {
if (this == null) {
throw new TypeError('Array.prototype.myForEach called on null or undefined');
}
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
const O = Object(this);
const len = parseInt(O.length) || 0;
for (let k = 0; k < len; k++) {
if (k in O) {
callback.call(thisArg, O[k], k, O);
}
}
// forEach总是返回undefined
return undefined;
};
// 测试forEach
[1, 2, 3, 4, 5].myForEach((value, index) => {
console.log(`forEach: ${index} -> ${value}`);
});
console.log('=== 手写find方法 ===');
Array.prototype.myFind = function(callback, thisArg) {
if (this == null) {
throw new TypeError('Array.prototype.myFind called on null or undefined');
}
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
const O = Object(this);
const len = parseInt(O.length) || 0;
for (let k = 0; k < len; k++) {
if (k in O) {
const kValue = O[k];
if (callback.call(thisArg, kValue, k, O)) {
return kValue;
}
}
}
return undefined;
};
// 测试find
const findResult = [1, 2, 3, 4, 5].myFind(x => x > 3);
console.log('find结果:', findResult); // 4
console.log('=== 手写findIndex方法 ===');
Array.prototype.myFindIndex = function(callback, thisArg) {
if (this == null) {
throw new TypeError('Array.prototype.myFindIndex called on null or undefined');
}
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
const O = Object(this);
const len = parseInt(O.length) || 0;
for (let k = 0; k < len; k++) {
if (k in O) {
const kValue = O[k];
if (callback.call(thisArg, kValue, k, O)) {
return k;
}
}
}
return -1;
};
// 测试findIndex
const findIndexResult = [1, 2, 3, 4, 5].myFindIndex(x => x > 3);
console.log('findIndex结果:', findIndexResult); // 3
console.log('=== 手写some方法 ===');
Array.prototype.mySome = function(callback, thisArg) {
if (this == null) {
throw new TypeError('Array.prototype.mySome called on null or undefined');
}
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
const O = Object(this);
const len = parseInt(O.length) || 0;
for (let k = 0; k < len; k++) {
if (k in O) {
const kValue = O[k];
if (callback.call(thisArg, kValue, k, O)) {
return true;
}
}
}
return false;
};
// 测试some
const someResult = [1, 2, 3, 4, 5].mySome(x => x > 3);
console.log('some结果:', someResult); // true
console.log('=== 手写every方法 ===');
Array.prototype.myEvery = function(callback, thisArg) {
if (this == null) {
throw new TypeError('Array.prototype.myEvery called on null or undefined');
}
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
const O = Object(this);
const len = parseInt(O.length) || 0;
for (let k = 0; k < len; k++) {
if (k in O) {
const kValue = O[k];
if (!callback.call(thisArg, kValue, k, O)) {
return false;
}
}
}
return true;
};
// 测试every
const everyResult = [2, 4, 6, 8, 10].myEvery(x => x % 2 === 0);
console.log('every结果:', everyResult); // true
console.log('=== 综合测试 ===');
// 综合使用测试
const testData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.log('原数组:', testData);
console.log('map翻倍:', testData.myMap(x => x * 2));
console.log('filter偶数:', testData.myFilter(x => x % 2 === 0));
console.log('reduce求和:', testData.myReduce((acc, curr) => acc + curr, 0));
console.log('find大于5:', testData.myFind(x => x > 5));
console.log('some大于8:', testData.mySome(x => x > 8));
console.log('every小于15:', testData.myEvery(x => x < 15));
// 链式调用测试
const chainResult = testData
.myFilter(x => x % 2 === 0) // 过滤偶数
.myMap(x => x * x) // 平方
.myReduce((acc, curr) => acc + curr, 0); // 求和
console.log('链式调用结果:', chainResult); // 2²+4²+6²+8²+10² = 4+16+36+64+100 = 220通过本节JavaScript手写实现数组方法的学习,你已经掌握:
"手写数组方法不仅是面试的重点,更是深入理解JavaScript底层机制的重要途径。掌握这些实现将让你对数组操作有更深刻的理解!"