这篇文章不是向您展示如何使用forEach , map , filter和reduce , NOR也不是显示高阶函数的好处的文章。 还有很多其他 文章 可以 做到这一点。
当我想了解一个基本概念时,我喜欢尝试自己实现 ,即使它是一个非常基本的版本。 人们以不同的方式学习,这对我有用。 因此,在尝试创建自己的JavaScript中的forEach , map , reduce和filter函数的过程中,让我带您forEach这一旅程。
myForEach
为了消除内置的forEach歧义,我们将其称为新函数myForEach 。
首先要做的是查看呼叫签名。 因此,您知道的forEach函数会执行以下操作:
let arr = [ 1, 2, 3, 4 ]
// log each item of the array
arr.forEach(console.log)
// double each item of the array and log it to the console
arr.forEach((val) => console.log(val * 2))
我们正在寻找一种带有两个参数的函数,即数组和回调函数。 回调函数可以是函数定义或匿名函数。 我们想要执行的操作是遍历数组中的每个项目,然后对该项目调用回调。 因此,让我们试一试。
function myForEach(arr, callback) {
for (let i = 0; i < arr.length; i++) {
callback(arr[i])
}
}let arr = [ 1, 2, 3, 4 ]
// log each item of the array
myForEach(arr, console.log)
// double each item of the array and log it to the console
myForEach(arr, val => console.log(val * 2))
看起来很简单,并且可以正常工作。 但是……但是……并不完全一样。 如果尝试使用arr.myForEach(console.log) ,则会收到TypeError: arr.myForEach is not a function 。 这是因为我们将myForEach声明为全局函数。 实际上,它应该是Array对象原型的一部分。 注意本文的目的不是解释原型继承如何工作。 为了实现我们想要的,我们需要执行以下操作。
Array.prototype.myForEach = (callback) => {
for (let i = 0; i < this.length; i++) {
callback(arr[i])
}
}// log each item of the array
arr.myForEach(console.log)
当我尝试运行上面的代码时,没有任何反应。 怎么了?
试图看中箭头功能。 箭头函数没有自己的this或arguments绑定。 因此,当我在函数定义中调用this函数时,它并不像我天真的期望的那样指向Array对象本身。 这是正确的方法。 这个StackOverflow帖子有解释。
Array.prototype.myForEach = function (callback) {
for (let i = 0; i < arr.length; i++) {
callback(arr[i])
}
}// log each item of the array
arr.myForEach(console.log)
美丽。 现在我们已经掌握了一些技巧,接下来可以继续使用其他更高阶的函数。
myMap
签名如下。 假设我们有一个数字数组,并且想要得到这些数字的平方的数组。
let arr = [ 1, 2, 3, 4 ]
let arrNew = arr.map(val => val * val)
// or
const squareNumber = n => n * n
let arrNew2 = arr.map(squareNumber)
console.log(arrNew)
// outputs [ 1, 4, 9, 16 ]
因此,我们将其应用于一个数组:一个具有回调并返回新数组的函数。 回调是一个接受输入值的函数,通常将该输入值转换为其他值。
Array.prototype.myMap = function(callback) {
let ret = []
for (var i = 0; i < this.length; i++) {
let newVal = callback(this[i])
ret.push(newVal)
}
return ret
} 我们遍历数组(由this表示),应用回调函数,并将结果值推送到新数组上。 然后返回该新数组。
我们可以使用新的myForEach函数更简洁地编写此代码。
Array.prototype.myMap = function(callback) {
let ret = []
this.myForEach(val => ret.push(callback(val)))
return ret
}继续。
myFilter
到目前为止,我们已经开始掌握它了。 这是过滤器调用签名。
let people = [
{ 'name': 'Bob', 'age': 70 },
{ 'name': 'Sue', 'age': 30 },
{ 'name': 'Joe', 'age': 18 } ]
let youngPeople = people.filter( person => person.age < 69 )
// returns array with two objects.
// [ { 'name': 'Sue', 'age': 30 },
// { 'name': 'Joe', 'age': 18 } ]
// or
const isYoung = person => person.age < 59
let youngPeople = people.filter(isYoung)
因此,过滤器需要一个返回true或false的回调,也称为谓词。 该函数返回一个新数组。 根据我们的经验,我们可以快速完成这项工作。
Array.prototype.myFilter = function(callback) {
let ret = []
this.myForEach( (val) => {
if (callback(val) === true) ret.push(val)
}
return ret
} 非常简单。 注意,我本可以编写if (callback(val))而不是if (callback(val) === true) ,但是这样做是为了强调我们希望回调返回true或false 。
myReduce
reduce函数对于grok来说有点复杂。 减少的教科书示例是对数组的元素求和。 这就是它的样子。
let arr = [ 1, 2, 3, 4 ]
let sum = arr.reduce( (acc, val) => acc + val )
console.log(sum)
// prints out 10
回调有两个参数,第一个通常称为累加器,第二个是数组中特定项的值。 在此示例中,累加器保持运行总计,并且每次对数组进行迭代时,其值都将添加到累加器中。 最后,累加器包含我们试图获取的值。
这是函数最基本的形式。
Array.prototype.myReduce = function (callback) {
let ret
this.myForEach( val => {
if (ret === undefined) ret = val
else ret = callback(ret, val)
}
return ret
} 这比我们上面定义的其他高阶函数要复杂一些。 请注意我们是否需要检查ret是否undefined 。 这又提出了另一点。 JavaScript reduce函数可让您指定初始值。 这不难添加到我们的函数中。 我们只是将init参数添加到函数定义中,然后将ret变量初始设置为init 。
Array.prototype.myReduce = function (callback, init) {
let ret = init
this.myForEach( val => {
if (ret === undefined) ret = val
else ret = callback(ret, val)
}
return ret
}const sumTwoValues = (a, b) => a + b
let sum = arr.myReduce( sumTwoValues , 0 ) // added 0 as an argument for initial value.
console.log(sum)
// prints 10
改进措施
对于上面的每个函数,我们都可以使其在回调签名中包括数组元素的索引,并使其在回调中可用。 这应该使我们能够进行以下操作。
var arr = [ 1, 2, 3, 4, 5, 6 ]
arr.forEach( (val, idx) => { if (idx % 2 === 0) console.log(val) }
// prints the value of every other item in the array.arr.map( (val, idx) => idx % 2 === 0 ? val * 2 : val * 3 )
// create a new array where every even item has been doubled and every odd item has been trippled
arr.map( (val, idx) => idx == 0)
// return an array with only the first item
实现非常容易。 在myForEach函数中,我们将值和索引传递给回调。 而且由于我们在所有其他函数中都使用了myForEach,因此我们可以轻松地将索引包括在回调调用中。
Array.prototype.myForEach = function(callback) {
for (var i = 0; i < this.length; i++) {
callback(this[i], i)
}
}Array.prototype.myMap = function(callback) {
let ret = []
this.myForEach((val, idx) => ret.push(callback(val, idx)))
return ret
}Array.prototype.myFilter = function(callback) {
let ret = []
this.myForEach((val, idx) => { if (callback(val, idx)) ret.push(val)} )
return ret
}Array.prototype.myReduce = function(callback, init) {
let ret = init
this.myForEach( ( val, idx ) => {
if (ret === undefined) ret = val
else ret = callback(ret, val, idx)
})
return ret
}我们应该做的另一件事是一些错误检查。 例如,我们应该确保回调是一个函数。 我们应该检查数组是否不为空。
if (this == null) {
throw new TypeError('array is null')
}
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function'
} if (this == null) {
throw new TypeError('array is null')
}
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function'
}
if (this == null) {
throw new TypeError('array is null')
}
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function'
}
if (this == null) {
throw new TypeError('array is null')
}
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function'
}
到此为止。 实际的Javascript forEach , map , filter和reduce函数更加复杂。 但是,通过对这些基本实现进行编码,您可能会更好地了解它们如何在后台运行。
如果您喜欢它,请给我一些爱,然后单击下面的心。
From: https://hackernoon.com/higher-order-functions-behind-the-scenes-5853179cfd9c