引言
在JavaScript开发中,数组方法是日常编码的核心工具。理解这些方法的内部实现原理不仅能帮助我们写出更高效的代码,还能在面试中展现扎实的基础。本文将完整实现JavaScript中最重要、最常用的数组方法,涵盖高阶函数、搜索方法、扁平化方法和排序算法。
一、高阶函数实现
1.1 map方法实现
map是最常用的高阶函数之一,它创建一个新数组,其结果是该数组中的每个元素调用一次提供的函数后的返回值。
javascript
Array.prototype.myMap = function (callback, thisArg) {
// 输入验证
if (this === null) {
throw new TypeError("this is null or not defined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + "is not a function");
}
const obj = Object(this);
const len = obj.length >>> 0;
const result = new Array(len);
// 遍历并执行回调
for (let i = 0; i < len; i++) {
// 处理稀疏数组
if (i in obj) {
result[i] = callback.call(thisArg, obj[i], i, obj);
}
}
return result;
};
// 使用示例
const numbers = [1, 2, 3];
const squares = numbers.myMap((num) => num * num);
console.log(squares); // [1, 4, 9]
1.2 filter方法实现
filter方法创建一个新数组,包含通过测试的所有元素。
javascript
Array.prototype.myFilter = function (callback, thisArg) {
if (this === null) {
throw new TypeError("this is null or not defined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
const obj = Object(this);
const len = obj.length >>> 0;
const result = [];
for (let i = 0; i < len; i++) {
if (i in obj) {
// 如果回调返回true,则保留该元素
if (callback.call(thisArg, obj[i], i, obj)) {
result.push(obj[i]);
}
}
}
return result;
};
// 使用示例:筛选出大于2的数字
const nums = [1, 2, 3, 4, 5];
const filtered = nums.myFilter((num) => num > 2);
console.log(filtered); // [3, 4, 5]
1.3 reduce方法实现
reduce是最强大的高阶函数,可以将数组元素通过reducer函数累积为单个值。
javascript
Array.prototype.myReduce = function (callback, initialValue) {
if (this === null) {
throw new TypeError("this is null or not defined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
const obj = Object(this);
const len = obj.length >>> 0;
// 处理空数组且无初始值的情况
if (len === 0 && initialValue === undefined) {
throw new TypeError("Reduce of empty array with no initial value");
}
let accumulator = initialValue;
let startIndex = 0;
// 如果没有提供初始值,使用第一个有效元素作为初始值
if (initialValue === undefined) {
// 找到第一个存在的元素(处理稀疏数组)
while (startIndex < len && !(startIndex in obj)) {
startIndex++;
}
if (startIndex === len) {
throw new TypeError("Reduce of empty array with no initial value");
}
accumulator = obj[startIndex];
startIndex++;
}
// 执行reduce操作
for (let i = startIndex; i < len; i++) {
if (i in obj) {
accumulator = callback(accumulator, obj[i], i, obj);
}
}
return accumulator;
};
// 使用示例
const sum = [1, 2, 3, 4, 5].myReduce((acc, curr) => acc + curr, 0);
console.log(sum); // 15
// 复杂示例:数组转对象
const items = [
{ id: 1, name: "Apple" },
{ id: 2, name: "Banana" },
{ id: 3, name: "Orange" },
];
const itemMap = items.myReduce((acc, item) => {
acc[item.id] = item;
return acc;
}, {});
console.log(itemMap);
// {
// '1': { id: 1, name: 'Apple' },
// '2': { id: 2, name: 'Banana' },
// '3': { id: 3, name: 'Orange' }
// }
二、搜索与断言方法
2.1 find方法实现
find方法返回数组中满足测试函数的第一个元素的值。
javascript
Array.prototype.myFind = function (callback, thisArg) {
if (this === null) {
throw new TypeError("this is null or not defined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
const obj = Object(this);
const len = obj.length >>> 0;
for (let i = 0; i < len; i++) {
if (i in obj) {
if (callback.call(thisArg, obj[i], i, obj)) {
return obj[i];
}
}
}
return undefined;
};
// 使用示例
const users = [
{ id: 1, name: "Alice", age: 25 },
{ id: 2, name: "Bob", age: 30 },
{ id: 3, name: "Charlie", age: 35 },
];
const user = users.myFind((user) => user.age > 28);
console.log(user); // { id: 2, name: 'Bob', age: 30 }
2.2 findIndex方法实现
findIndex方法返回数组中满足测试函数的第一个元素的索引。
javascript
Array.prototype.myFindIndex = function (callback, thisArg) {
if (this === null) {
throw new TypeError("this is null or not defined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
const obj = Object(this);
const len = obj.length >>> 0;
for (let i = 0; i < len; i++) {
if (i in obj) {
if (callback.call(thisArg, obj[i], i, obj)) {
return i;
}
}
}
return -1;
};
// 使用示例
const numbers = [5, 12, 8, 130, 44];
const firstLargeNumberIndex = numbers.myFindIndex(num => num > 10);
console.log(firstLargeNumberIndex); // 1
2.3 some方法实现
some方法返回数组中是否至少有一个元素通过了测试。
javascript
Array.prototype.mySome = function (callback, thisArg) {
if (this === null) {
throw new TypeError("this is null or not defined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
const obj = Object(this);
const len = obj.length >>> 0;
for (let i = 0; i < len; i++) {
if (i in obj) {
if (callback.call(thisArg, obj[i], i, obj)) {
return true;
}
}
}
return false;
};
// 使用示例
const hasEven = [1, 3, 5, 7, 8].mySome((num) => num % 2 === 0);
console.log(hasEven); // true
2.4 every方法实现
every方法测试数组中的所有元素是否都通过了测试。
javascript
Array.prototype.myEvery = function (callback, thisArg) {
if (this === null) {
throw new TypeError("this is null or not defined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
const obj = Object(this);
const len = obj.length >>> 0;
for (let i = 0; i < len; i++) {
if (i in obj) {
if (!callback.call(thisArg, obj[i], i, obj)) {
return false;
}
}
}
return true;
};
// 使用示例
const allPositive = [1, 2, 3, 4, 5].myEvery((num) => num > 0);
console.log(allPositive); // true
三、数组扁平化方法
3.1 flat方法实现
flat方法创建一个新数组, 其中所有子数组元素递归连接到指定深度。
javascript
Array.prototype.myFlat = function (depth = 1) {
if (this === null) {
throw new TypeError("this is null or not defined");
}
// 深度参数验证
if (depth < 0) {
throw new RangeError("depth must be a non-negative integer");
}
const result = [];
const flatten = (arr, currentDepth) => {
for (let i = 0; i < arr.length; i++) {
const element = arr[i];
// 如果当前深度小于指定深度且元素是数组, 则递归扁平化
if (Array.isArray(element) && currentDepth < depth) {
flatten(element, currentDepth + 1);
} else {
// 否则直接添加到结果数组
// 注意: 如果depth为0,则不会扁平化任何数组
result.push(element);
}
}
};
flatten(this, 0);
return result;
};
// 使用示例
const nestedArray = [1, [2, [3, [4]], 5]];
console.log(nestedArray.myFlat()); // [1, 2, [3, [4]], 5]
console.log(nestedArray.myFlat(2)); // [1, 2, 3, [4], 5]
console.log(nestedArray.myFlat(Infinity)); // [1, 2, 3, 4, 5]
3.2 flatMap方法实现
flatMap方法首先使用映射函数映射每个元素, 然后将结果压缩成一个新数组。
javascript
Array.prototype.myFlatMap = function (callback, thisArg) {
if (this === null) {
throw new TypeError("this is null or not defined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
const obj = Object(this);
const len = obj.length >>> 0;
const result = [];
for (let i = 0; i < len; i++) {
if (i in obj) {
const mapped = callback.call(thisArg, obj[i], i, obj);
// 如果回调函数返回的是数组, 则展开它
if (Array.isArray(mapped)) {
for (let j = 0; j < mapped.length; j++) {
result.push(mapped[j]);
}
} else {
// 如果不是数组,直接添加
result.push(mapped);
}
}
}
return result;
};
// 使用示例
const phrases = ["Hello world", "JavaScript is awesome"];
const words = phrases.myFlatMap((phrase) => phrase.split(" "));
console.log(words); // ["Hello", "world", "JavaScript", "is", "awesome"]
// 另一个示例:展开并过滤
const numbers2 = [1, 2, 3, 4];
const result = numbers2.myFlatMap((x) => (x % 2 === 0 ? [x, x * 2] : []));
console.log(result); // [2, 4, 4, 8]
四、排序算法实现
4.1 sort方法实现
JavaScript原生的sort方法使用TimSort算法(一种混合排序算法, 结合了归并排序和插入排序)。这里我们实现一个简单但功能完整的排序方法, 支持自定义比较函数。
javascript
Array.prototype.mySort = function (compartFn) {
if (this === null) {
throw new TypeError("this is null or not defined");
}
const obj = Object(this);
const len = obj.length >>> 0;
// 如果没有提供比较函数, 使用默认的字符串比较
if (compartFn === undefined) {
// 默认比较函数: 将元素转为字符串, 然后比较UTF-16代码单元值序列
compartFn = function (a, b) {
const aString = String(a);
const bString = String(b);
if (aString < bString) return -1;
if (aString > bString) return 1;
return 0;
};
} else if (typeof compartFn !== "function") {
throw new TypeError("compareFn must be a function or undefined");
}
// 实现快速排序算法(高效且常用)
function quickSort(arr, left, right, compare) {
if (left >= right) return;
const pivotIndex = partition(arr, left, right, compare);
quickSort(arr, left, pivotIndex - 1, compare);
quickSort(arr, pivotIndex + 1, right, compare);
}
function partition(arr, left, right, compare) {
// 选择中间元素作为基准值
const pivotIndex = Math.floor((left + right) / 2);
const pivotValue = arr[pivotIndex];
// 将基准值移到最右边
[arr[pivotIndex], arr[right]] = [arr[right], arr[pivotIndex]];
let storeIndex = left;
for (let i = left; i < right; i++) {
// 使用比较函数比较当前元素和基准值
if (compare(arr[i], pivotValue) < 0) {
[arr[storeIndex], arr[i]] = [arr[i], arr[storeIndex]];
storeIndex++;
}
}
// 将基准值放到正确的位置
[arr[storeIndex], arr[right]] = [arr[right], arr[storeIndex]];
return storeIndex;
}
// 将稀疏数组转换为紧凑数组(跳过不存在的元素)
const compactArray = [];
for (let i = 0; i < len; i++) {
if (i in obj) {
compactArray.push(obj[i]);
}
}
// 执行快速排序
if (compactArray.length > 0) {
quickSort(compactArray, 0, compactArray.length - 1, compartFn);
}
// 将排序后的数组复制回原数组,保持稀疏性
let compactIndex = 0;
for (let i = 0; i < len; i++) {
if (i in obj) {
obj[i] = compactArray[compactIndex++];
}
}
return obj;
};
// 使用示例
const unsorted = [3, 1, 4, 1, 5, 9, 2, 6, 5];
unsorted.mySort();
console.log(unsorted); // [1, 1, 2, 3, 4, 5, 5, 6, 9]
// 使用自定义比较函数
const students = [
{ name: "Alice", score: 85 },
{ name: "Bob", score: 92 },
{ name: "Charlie", score: 78 },
];
students.mySort((a, b) => b.score - a.score);
console.log(students);
// 按分数降序排列
五、总结
5.1 实现要点总结
- 输入验证: 始终检查
this是否为null或undefined, 以及回调函数是否为函数类型 - 稀疏数组处理: 使用
in操作符检查索引是否存在 - 类型安全: 使用
>>>0确保长度为非负整数 - 性能考虑:
- 避免不必要的数组拷贝
- 使用适当的算法(如快速排序对于sort方法)
- 注意递归深度(特别是对于flat方法)
- 与原生方法差异:
- 我们的实现在某些边缘情况下可能与原生方法略有不同
- 原生方法通常有更好的性能和内存管理
5.2 实际应用场景
- 数据处理:
map、filter、reduce是数据处理的三件套 - 搜索功能:
find、findIndex用于数据检索 - 表单验证:
some、every用于验证多个输入 - 状态管理:
flat、flatMap在处理嵌套状态时特别有用 - 数据展示:
sort用于数据排序
通过手动实现这些核心数组方法,我们不仅加深了对JavaScript数组操作的理解,还掌握了函数式编程的核心概念。
记住:在实际生产环境中,仍然建议使用原生数组方法,因为它们经过了充分优化和测试。但理解这些方法的实现原理,将使你成为一个更出色的JavaScript开发者。