如何从头开始实现 JavaScript 数组方法

介绍

JavaScript 包含多个用于处理超出for循环的数组的函数您可能在自己的项目中使用过这些函数,并想知道它们是如何工作的以及为什么要使用它们。

了解某事如何运作的一个好方法是从头开始构建您自己的版本。在本文中,您将通过创建自己的版本,要做到这一点mapfiltersort,并reduce从头开始。完成后,您将对这些功能及其用途有更好的了解。

通过将 ES6 箭头函数与 JavaScript 数组函数相结合,您可以编写非常强大和干净的代码。

JavaScript 数组方法的工作原理

让我们从一个例子开始。假设您要遍历一个数字数组,将每个元素加一,然后返回新数组。过去,您需要做几件事来完成此操作:

  • 初始化一个新的空数组。
  • 遍历原始数组中的每个元素。
  • 更改该元素并将更改后的值放入新数组中。

代码如下所示:

const arr = [1, 2, 3];
const newArray = [];

for (let i = 0; i < arr.length; i++) {
    newArray[i] = arr[i] + 1;
}

return newArray;

但是使用内置map函数,您可以在一行代码中完成此操作:

return arr.map(element => ++element);

JavaScript 数组方法大量利用ES6 箭头函数

我们将介绍的每个 Array 函数都接受一个函数作为参数。他们将遍历数组的每个元素并调用该函数来确定对每个元素做什么。遍历每个元素并调用回调函数后,将返回一个新的数组或项目。

先决条件

要在本地学习本教程,您需要一个编辑器(例如Visual Studio Code)和一个沙箱环​​境扩展(例如Quokka.js)。

要在线学习教程,您可以使用CodePenCodeSandbox

第 1 步 – 实施地图

map 遍历每个元素,以某种方式对其进行转换,将其添加到新数组中,然后返回新数组。

警告:在本文中,您将使用自定义函数扩展 JavaScript 全局对象。这仅用于教育目的,因为这种做法有可能在生产代码中引入副作用。

JavaScript 数组函数是 Array 原型的一部分,例如类似于 Java 中类上的函数。要覆盖它们,您可以为 分配一个新函数Array.prototype

让我们创建一个新函数并将其分配给Array.prototype.mapFromScratch

const myCustomMapFunction = function(callback) {
    console.log('My Custom Map Function!');
}
Array.prototype.mapFromScratch = myCustomMapFunction;

const arr = [1, 2, 3];

arr.mapFromScratch();

如果运行此代码,您将在控制台中看到日志消息:

Output
My Custom Map Function!

现在,添加for循环并打印出每个元素。由于数组本身调用该方法,您可以通过引用访问该数组this

const myCustomMapFunction = function(callback) {
    console.log('My Custom Map Function!');

    // 'this' refers to the array
    for (let i = 0; i < this.length; i++) {
        console.log(this[i]);
    }

}

现在,通过调用回调函数执行任何所需的转换。当你这样做时,你会将当前元素和当前索引传递给它:

const myCustomMapFunction = function(callback) {
    console.log('My Custom Map Function!');

    // 'this' refers to the array
    for (let i = 0; i < this.length; i++) {
        const transformedElement = callback([this[i], i);
    }

}

最后,将转换后的元素添加到一个新数组并返回该数组。

const myCustomMapFunction = function(callback) {
    console.log('My Custom Map Function!');

    const newArray = [];

    // 'this' refers to the array
    for (let i = 0; i < this.length; i++) {
        newArray[i] = callback(this[i], i);
    }

    return newArray;
}

让我们通过一个函数来测试你重写的函数,该函数将增加数组中的每个值:

// arr = [1, 2, 3]
// expected = [2, 3, 4]

console.log(arr.mapFromScratch((element) => ++element));

您将收到以下输出:

Output
My Custom Map Function! [2, 3, 4]

在此步骤中,您实现了一个自定义map函数。接下来,让我们探索实现一个filter功能。

第 2 步 – 实现过滤器

filter函数返回一个从原始数组中过滤出来的新元素数组。

让我们创建一个新函数并将其分配给Array.prototype.filterFromScratch

const myCustomFilterFunction = function(callback) {
    console.log('My Custom Filter Function!');
}
Array.prototype.filterFromScratch = myCustomFilterFunction;

const arr = [1, 2, 3];

arr.filterFromScratch();

现在,设置for循环以遍历每个元素:

const myCustomFilterFunction = function(callback) {
    console.log('My Custom Filter Function!');

    const newArray = [];

    for (let i = 0; i < this.length; i++) {
        console.log(this[i]);
    }
}

for循环内部,您需要决定是否将每个元素添加到新数组中。这就是回调函数的用途,因此您可以使用它来有条件地添加每个元素。如果返回值为true,则将元素推送到返回数组:

const myCustomFilterFunction = function(callback) {
    console.log('My Custom Filter Function!');

    const newArray = [];

    for (let i = 0; i < this.length; i++) {
        if (callback(this[i])) {
            newArray.push(this[i]);
        }
    }

    return newArray;
}

让我们通过使用将显示大于 的值的函数对其进行测试来看看您重写的函数的实际效果1

// arr = [1, 2, 3]
// expected = [2, 3]

console.log(arr.filterFromScratch((element) =>  element > 1));

您将收到以下输出:

Output
My Custom Filter Function! [2, 3]

这样,您就实现了一个自定义filter函数。接下来,您将使用该sort函数。

第 3 步 – 实现排序

sort函数从原始数组返回一个已排序的数组。

让我们创建一个新函数并将其分配给Array.prototype.sortFromScratch

const myCustomSortFunction = function(callback) {
    console.log('My Custom Sort Function!');
}
Array.prototype.sortFromScratch = myCustomSortFunction;

const arr = [3, 2, 1];

arr.sortFromScratch();

我们将使用冒泡排序来实现这种排序。这是我们将采取的方法:

  • 重复遍历数组中的项目。
  • 比较相邻的项目,如果它们不按顺序交换。
  • 在对数组进行足够次数的迭代以进行每次比较后,对数组进行排序。

使用冒泡排序,您必须为数组中的每个元素完全遍历数组一次。这需要一个嵌套for循环,其中内部循环通过停止距离最后一个元素进行迭代,所以现在让我们添加它。

注意:这是出于教育目的,并不是一种有效的排序方法。

const myCustomSortFunction = function(callback) {
    console.log('My Custom Sort Function!');

    const newArray = [];

    for (let i = 0; i < newArray.length; i++) {
        for (let j = 0; j < newArray.length - 1; j++) { 
        }
    }
}

您也不想更改原始数组。为避免这种情况,您可以使用扩展运算符将原始数组复制到新数组中

const myCustomSortFunction = function(callback) {
    console.log('My Custom Sort Function!');

    const newArray = [...this];

    for (let i = 0; i < newArray.length; i++) {
        for (let j = 0; j < newArray.length - 1; j++) { 
        }
    }
}

回调函数接受两个参数,当前元素和下一个元素,无论它们是否有序都会返回。在我们的例子中,如果回调函数返回一个大于 的数字0,我们希望交换两个元素。

const myCustomSortFunction = function(callback) {
    console.log('My Custom Sort Function!');

    const newArray = [...this];

    for (let i = 0; i < newArray.length; i++) {
        for (let j = 0; j < newArray.length - 1; j++) {
            if (callback(newArray[j], newArray[j + 1]) > 0) {
                // swap the elements
            }
        }
    }
}

要交换元素,您需要复制一个元素,替换第一个元素,然后用副本替换第二个元素。完成后,它返回新排序的数组。

const myCustomSortFunction = function(callback) {
    console.log('My Custom Sort Function!');

    const newArray = [...this]; 

    for (let i = 0; i < newArray.length; i++){
        for (let j = 0; j < newArray.length - 1; j++) {
            if (callback(newArray[j], newArray[j + 1]) > 0) {
                const temp = newArray[j + 1];
                newArray[j + 1] = newArray[j];
                newArray[j] = temp;
            }
       }
    }

    // array is sorted
    return newArray;
}

让我们通过使用从低到高快速排序的函数对其进行测试,来看看您重写的函数的实际运行情况:

// arr = [3, 2, 1]
// expected = [1, 2, 3]

console.log(arr.sortFromScratch((current, next) => current - next));

您将收到以下输出:

Output
My Custom Sort Function! [1, 2, 3]

现在您已经创建了一个自定义sort函数,您已准备好继续实现一个reduce函数。

第 4 步 – 实施 Reduce

reduce函数遍历每个元素并返回一个值。

reduce不像其他函数那样返回一个新数组。它实际上将数组中的元素“减少”为一个最终值:数字、字符串或对象。使用的最常见原因之一reduce是当您想对数字数组中的所有元素求和时。

让我们创建一个新函数并将其分配给Array.prototype.reduceFromScratch

const myCustomReduceFunction = function(callback) {
    console.log('My Custom Reduce Function!');
}
Array.prototype.reduceFromScratch = myCustomReduceFunction;

const arr = [1, 2, 3];

arr.reduceFromScratch();

为了reduce返回一个最终值,它需要一个起始值来处理。用户传递的回调函数将根据数组的每个元素确定如何更新此累加器并在最后返回它。回调函数必须返回更新的累加器。

for现在添加您的循环,并调用回调函数。返回值成为新的累加器。循环结束后,返回累加器。

const myCustomReduceFunction = function(callback, accumulator) {
    console.log('My Custom Reduce Function!');
    for (let i = 0; i < this.length; i++) {
        accumulator = callback(accumulator, this[i]);
    }
    return accumulator;
}

让我们通过一个对数组内容求和的函数进行测试,来看看您重写的函数的实际运行情况:

// arr = [1, 2, 3]
// expected = 6

console.log(arr.reduceFromScratch((accumulator, element) => accumulator + element, 0));
Output
My Custom Reduce Function! 6

结论

JavaScript 的数组函数非常有用。在本教程中,您重新实现了数组函数以更好地理解它们的工作原理,从而可以更有效地使用它们。

觉得文章有用?

点个广告表达一下你的爱意吧 !😁