JavaScript 数组原生方法手写实现

引言

在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 实现要点总结
  1. 输入验证: 始终检查this是否为nullundefined, 以及回调函数是否为函数类型
  2. 稀疏数组处理: 使用in操作符检查索引是否存在
  3. 类型安全: 使用>>>0确保长度为非负整数
  4. 性能考虑:
  • 避免不必要的数组拷贝
  • 使用适当的算法(如快速排序对于sort方法)
  • 注意递归深度(特别是对于flat方法)
  1. 与原生方法差异:
  • 我们的实现在某些边缘情况下可能与原生方法略有不同
  • 原生方法通常有更好的性能和内存管理
5.2 实际应用场景
  1. 数据处理: mapfilterreduce是数据处理的三件套
  2. 搜索功能: findfindIndex用于数据检索
  3. 表单验证: someevery用于验证多个输入
  4. 状态管理: flatflatMap在处理嵌套状态时特别有用
  5. 数据展示: sort用于数据排序

通过手动实现这些核心数组方法,我们不仅加深了对JavaScript数组操作的理解,还掌握了函数式编程的核心概念。

记住:在实际生产环境中,仍然建议使用原生数组方法,因为它们经过了充分优化和测试。但理解这些方法的实现原理,将使你成为一个更出色的JavaScript开发者。

相关推荐
rockmelodies2 小时前
CVE-2025-55182:React Server Components 断点跟踪
前端·react.js·前端框架
草帽lufei2 小时前
3大免费AI工具实战测评,用提示词“调教”出业务大屏
前端·ai编程·trae
汉堡大王95272 小时前
JavaScript类型变形记:当代码开始“不正经”地转换身份
前端·javascript
Miss妤2 小时前
Gemini写应用(二)
前端
用户93051065822242 小时前
自造微前端
前端·javascript
之恒君2 小时前
寄生组合继承 vs ES6 类继承 深度对比
前端·javascript
涔溪2 小时前
整理vue3+ vite 开发经常会遇到的问题。
前端·vue.js·typescript
用户51681661458412 小时前
script 标签的异步加载:async、defer、type="module" 详解
前端·javascript
m0_471199632 小时前
【vue】dep.notify() 是什么意思?
前端·javascript·vue.js