JavaScript 中高阶函数的威力(附示例和用例)
在Medium上找到我
JavaScript 几乎在现有的每个应用程序中都使用函数。得益于函数,JavaScript 能够实现许多强大的功能,例如在医疗保健领域构建人工智能功能。
我将在本文中讨论一个强大的功能,它利用了函数,即所谓的高阶函数。高阶函数是指可以接受另一个函数作为参数,或返回另一个函数作为返回值的函数。我还将展示一些强大的示例和使用案例,而这正是大多数教程所缺少的。不过不用担心,你会在这篇文章中找到例子。
话虽如此,如果你玩过 JavaScript,你可能之前就听说过这个术语。高阶函数在 JavaScript 中被广泛使用,它们存在于常用的函数中,例如.map
、和。.filter
.reduce
.forEach
如果您对 JavaScript 还不熟悉,您可能会对这些方法中高阶函数的位置感到困惑。
当您将函数回调声明为这些数组方法的参数时,您会看到这些:
const arr = [1, 2, 3, 4, 5, 'six', 'seven', 'eight', 'nine', 'ten']
// Duplicate the array
arr.map(function(value) {
return value
})
// Return only the number types
arr.filter(function(value) {
return typeof value === 'number'
})
// Log each value to the console
arr.forEach(function(value) {
console.log(value)
})
// Add the numbers together, avoiding the string types
arr.reduce(function(acc, value) {
if (typeof value === 'number') {
acc += value
}
return acc
}, 0)
但是高阶函数不是你传递给诸如 之类的方法的函数.map
。诸如 之类的方法.map
才是高阶函数。
当我们提到高阶函数可以是将另一个函数作为参数的函数时,这正是传入函数时所做的事情。
下面是一个与该方法功能完全相同的实现.map
:
function map(callback) {
const result = []
for (let index = 0; index < this.length; index++) {
const currentItem = this[index]
const returnValue = callback(currentItem, index, this)
result.push(returnValue)
}
return result
}
查看代码片段,该参数与我们传递给之前展示的方法callback
的参数完全相同的函数:.map
// Duplicate the array
arr.map(function(value) {
return value
})
为了更准确地说,让我将这段代码重命名为与我们的map
函数实现相同的名称,以便您可以更清楚地看到它:
const callback = function(value) {
return value
}
// Duplicate the array
arr.map(callback)
// is the same callback used in our .map implementation:
function map(callback) {
const result = []
for (let index = 0; index < this.length; index++) {
const currentItem = this[index]
const returnValue = callback(currentItem, index, this)
result.push(returnValue)
}
return result
}
乍一看,这似乎是一种用 JavaScript 编写代码的无用方法。既然可以避免所有这些麻烦,在一个函数中一次性完成所有操作,为什么还要传入一个函数并返回另一个函数呢?
高阶函数带来的最大好处是可重用性和简洁性。但它们也受益于编写优美的代码。没错,JavaScript 中确实存在着丑陋的代码和优美的代码。
考虑到可重用性,它引入了一些非常强大的代码组合。
代码组合和强大的示例
现在我们知道了高阶函数在代码中是什么样子的,您可能想知道它们有哪些用例以及它们在哪里开始发挥作用。
假设我们有一个青蛙列表:
const frogsList = [
// Yes, these frogs are intelligent. They know how to use email
{
name: 'bobTheFrog',
email: 'froggy@gmail.com',
age: 2,
gender: 'Male',
widthOfTongue: 3,
},
{
name: 'hippoTheFrog',
email: 'hippo@gmail.com',
age: 10,
gender: 'Male',
widthOfTongue: 11,
},
{
name: 'sally',
email: 'sallyLipstick@gmail.com',
age: 5,
gender: 'Female',
widthOfTongue: 4,
},
{
name: 'george',
email: 'georgeRoseBowl@gmail.com',
age: 11,
gender: 'Male',
widthOfTongue: 3,
},
{
name: 'lisa',
email: 'lisaLovesGeorgeForever@gmail.com',
age: 19,
gender: 'Female',
widthOfTongue: 15,
},
{
name: 'kentucky',
email: 'frogInKentucky@yahoo.com',
age: 18,
gender: 'Male',
widthOfTongue: 13,
},
]
为了不使用高阶函数将青蛙过滤为特定的性别类型,我们必须执行以下操作:
function filterGender(gender, frogs) {
return frogs.filter(function(frog) {
return frog.gender ==== gender
})
}
// filterGender in use
const maleFrogs = filterGender('Male', frogsList)
这完全没问题。但是,如果在一个应用程序中多次使用,可能会很麻烦。如果我们有一个关于青蛙的大型应用程序,filterGender
可能会用到不止一次。
更进一步
如果您要获取不同的青蛙列表,则必须filterGender
再次调用并重新声明您的性别作为过滤新列表的第一个参数:
function getFrogs() {
// some logic and returns a new list of frogs
}
const newFrogs = getFrogs()
const moreMaleFrogs = filterGender('Male', newFrogs) // Shucks, I have to write 'Male' again?
如果你从未听说过DRY原则,我强烈建议你了解一下。我们的代码片段因为第一个参数违反了这条规则。我们可以做得更好。
为了解决这个问题,我们可以使用高阶函数的概念。
function filterGender(gender) {
return function(frogs) {
return frogs.filter(function(frog) {
return frog.gender === gender
})
}
}
现在,就像这样,我们可以将这个性别过滤器分配给一个变量,这样我们就不必在过滤青蛙时声明相同的性别了!
const filterFemaleFrogs = filterGender('Female')
const femaleFrogs = filterFemaleFrogs(frogsList)
等等,这还不是全部。组合这些方法还有一个额外的好处。我们不仅不用再重写一个针对雌性青蛙的过滤器,还能重复使用返回的函数,从不同的青蛙列表中筛选出相同性别的青蛙!
现在我们可以从多个青蛙列表中筛选出雌性青蛙,而不必编写太多代码:
const frogsList = [
// Yes, these frogs are intelligent. They know how to use email
{
name: 'bobTheFrog',
email: 'froggy@gmail.com',
age: 2,
gender: 'Male',
widthOfTongue: 3,
},
{
name: 'hippoTheFrog',
email: 'hippo@gmail.com',
age: 10,
gender: 'Male',
widthOfTongue: 11,
},
{
name: 'sally',
email: 'sallyLipstick@gmail.com',
age: 5,
gender: 'Female',
widthOfTongue: 4,
},
{
name: 'george',
email: 'georgeRoseBowl@gmail.com',
age: 11,
gender: 'Male',
widthOfTongue: 3,
},
{
name: 'lisa',
email: 'lisaLovesGeorgeForever@gmail.com',
age: 19,
gender: 'Female',
widthOfTongue: 15,
},
{
name: 'kentucky',
email: 'frogInKentucky@yahoo.com',
age: 18,
gender: 'Male',
widthOfTongue: 13,
},
]
const frogsList2 = [
{
name: 'abc',
email: 'froggy@gmail.com',
age: 2,
gender: 'Male',
widthOfTongue: 1,
},
{
name: '123',
email: 'hippo@gmail.com',
age: 10,
gender: 'Male',
widthOfTongue: 4,
},
{
name: 'joe',
email: 'sallyLipstick@aol.com',
age: 5,
gender: 'Female',
widthOfTongue: 6,
},
{
name: 'jennifer',
email: 'georgeRoseBowl@aol.com',
age: 11,
gender: 'Female',
widthOfTongue: 10,
},
]
const frogsList3 = [
{
name: 'professorHammick',
email: 'froggy@gmail.com',
age: 2,
gender: 'Female',
widthOfTongue: 1,
},
{
name: 'macintosh',
email: 'hippo@gmail.com',
age: 10,
gender: 'Female',
widthOfTongue: 6,
},
{
name: 'frogger',
email: 'sallyLipstick@gmail.com',
age: 5,
gender: 'Female',
widthOfTongue: 4,
},
{
name: 'frogNation',
email: 'georgeRoseBowl@gmail.com',
age: 11,
gender: 'Female',
widthOfTongue: 4,
},
]
function gatherFemaleFrogsEverywhere(...frogLists) {
const allFemaleFrogs = []
const filterFemaleFrogs = filterGender('Female')
frogLists.forEach(function(list) {
allFemaleFrogs.push(...filterFemaleFrogs(list))
})
return allFemaleFrogs
}
const females = gatherFemaleFrogsEverywhere(frogsList, frogsList2, frogsList3)
进一步迈出第三步
如果你仍然不太相信JavaScript 语言中高阶函数有多么强大,那么让我们继续这个例子,创建一个更通用的函数来创建更高级别的可重用性:
function filterFrogs(filter) {
return function(frogs) {
return frogs.filter(filter)
}
}
之前,我们能够创建一个可复用的函数来区分青蛙的性别。现在,我们可以更进一步,抽象出该filter
函数的逻辑,这样我们就可以组合并复用不同的过滤函数了!
const filterMaleFrogs = filterFrogs(function(frog) {
return frog.gender === 'Male'
})
const filterAdultFrogs = filterFrogs(function(frog) {
return frog.age >= 10
})
const filterFrogNamesThatStartWithHippo = filterFrogs(function(frog) {
return frog.name.toLowerCase().startsWith('hippo')
})
const filterGmailEmails = filterFrogs(function(frog) {
return /gmail.com/i.test(frog.email)
})
哇!
之前,我们拥有令人惊叹的能力,可以重复使用性别过滤函数,而无需再次声明相同的性别类型。现在,我们又可以创建并重复使用过滤青蛙的函数,实现我们想要的效果!太棒了!
我们甚至可以同时使用它们:
function applyAllFilters(...filters) {
return function(frogs) {
let newFrogs = [...frogs]
for (let index = 0; index < filters.length; index++) {
const filter = filters[index]
newFrogs = filter(newFrogs)
}
return newFrogs
}
}
const applyFrogFilterers = applyAllFilters(
filterMaleFrogs,
filterAdultFrogs,
filterFrogNamesThatStartWithHippo,
filterGmailEmails,
)
const combinedFrogsList = [...frogsList, ...frogsList2, ...frogsList3]
const filteredFrogs = applyFrogFilterers(combinedFrogsList)
console.log(filteredFrogs)
/*
result:
{
age: 10,
email: "hippo@gmail.com",
gender: "Male",
name: "hippoTheFrog",
widthOfTongue: 11
}
*/
最后一次更进一步
我们的applyAllFilters
函数做得很好。然而,对于大量的青蛙列表,这可能会变得非常繁重,因为它需要运行filter
多次才能得到最终结果。
我们可以再次使用高阶函数的概念来创建一个简单的、可重用的、高阶函数,该函数能够通过同时应用过滤器来遍历整个青蛙列表。
为了更清楚地了解情况,请看一下for 循环代码并尝试了解幕后真正发生的事情:
function applyAllFilters(...filters) {
return function(frogs) {
let newFrogs = [...frogs]
for (let index = 0; index < filters.length; index++) {
const filter = filters[index]
newFrogs = filter(newFrogs)
}
return newFrogs
}
}
我希望你看一下这一行:
newFrogs = filter(newFrogs)
return frogs.filter(filter)
该行代码与此函数中的代码是同一行:
function filterFrogs(filter) {
return function(frogs) {
return frogs.filter(filter)
}
}
这是有问题的,因为 filter 方法创建了一个新数组。当我们这样写的时候:
const applyFrogFilterers = applyAllFilters(
filterMaleFrogs,
filterAdultFrogs,
filterFrogNamesThatStartWithHippo,
filterGmailEmails,
)
我们调用了 4 次 filter 方法。换句话说,我们让 JavaScript 在内存中创建四个不同的数组,以获得最终结果。
那么我们如何才能让 JavaScript 只创建一个数组来最终获得相同的结果呢?
你猜对了。使用高阶函数!
// NOTE: The filter functions are now individual functions (not wrapped with filterFrogs)
const filterMaleFrogs = function(frog) {
return frog.gender === 'Male'
}
const filterAdultFrogs = function(frog) {
return frog.age >= 10
}
const filterFrogNamesThatStartWithHippo = function(frog) {
return frog.name.toLowerCase().startsWith('hippo')
}
const filterGmailEmails = function(frog) {
return /gmail.com/i.test(frog.email)
}
// Credits to: SerjoA
function combineFilters(...fns) {
return function(val) {
for (let i = 0; i < fns.length; i++) {
const filter = fns[i]
const passes = filter(val)
if (passes) {
continue
} else {
return false
}
}
return true
}
}
function composeFrogFilterers(...fns) {
return function(frogs) {
// Credits to: SerjoA
return frogs.filter(combineFilters(...fns))
}
}
const applyFrogFilterers = composeFrogFilterers(
filterMaleFrogs,
filterAdultFrogs,
filterFrogNamesThatStartWithHippo,
filterGmailEmails,
)
const combinedFrogsList = [...frogsList, ...frogsList2, ...frogsList3]
const allFilteredFrogs = applyFrogFilterers(combinedFrogsList)
console.log(allFilteredFrogs)
/*
result:
{
age: 10,
email: "hippo@gmail.com",
gender: "Male",
name: "hippoTheFrog",
widthOfTongue: 11
}
*/
另外,感谢@serjoa为最后一个例子提供的精彩解决方法!
结论
我希望你能理解高阶函数的强大,并且通过阅读本文,你能对这个概念的用例有更深入的了解!敬请期待后续内容!
在Medium上找到我
文章来源:https://dev.to/jsmanifest/the-power-of-higher-order-functions-in-javascript-with-examples-and-use-cases-2hkl