如何从头开始实现JavaScript数组方法
介绍
JavaScript 包含几个用于处理超出 for
循环的数组的函数。 您可能已经在自己的项目中使用过这些功能,并且想知道它们是如何工作的,以及您为什么要使用其中的一个而不是另一个。
了解事物如何工作的一个好方法是从头开始构建您自己的版本。 在本文中,您将通过从头开始创建自己的 map
、filter
、sort
和 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)。
要在线学习教程,您可以使用 CodePen 或 CodeSandbox。
第 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();
如果您运行此代码,您将在控制台中看到日志消息:
OutputMy 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));
您将收到以下输出:
OutputMy 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));
您将收到以下输出:
OutputMy Custom Filter Function! [2, 3]
这样,您就实现了一个自定义的 filter
函数。 接下来,您将使用 sort
函数。
第三步——实现排序
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();
我们将使用 Bubble Sort 进行这种排序实现。 这是我们将采取的方法:
- 反复遍历数组中的项目。
- 比较相邻的项目,如果它们不按顺序交换。
- 在对数组进行足够多的迭代以进行每次比较之后,对数组进行排序。
使用冒泡排序,您必须对数组中的每个元素完全遍历数组一次。 这需要一个嵌套的 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++) { } } }
您也不想更改原始数组。 为避免这种情况,您可以使用 Spread Operator 将原始数组复制到新数组中。
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));
您将收到以下输出:
OutputMy 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
返回一个最终值,它需要一个起始值才能使用。 用户传递的回调函数将根据数组的每个元素确定如何更新这个 accumulator 并在最后返回。 回调函数必须返回更新的累加器。
现在添加您的 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));
OutputMy Custom Reduce Function! 6
结论
JavaScript 的数组函数非常有用。 在本教程中,您重新实现了数组函数以更好地了解它们的工作原理,从而更有效地使用它们。