英文 | https://medium.com/better-programming/5-examples-of-higher-order-functions-in-javascript-21aabb4c00ac
翻译 | web前端开发(ID:web_qdkf)
高阶函数可以说是JavaScript语言有史以来最好的事情之一。你有充分的理由广泛使用它们。
高阶函数就是指,函数可以作为参数被传递或者函数可以作为返回值输出的函数。
当我刚接触JavaScript时,我花了一段时间来了解它们的含义,因此,如果你处于这个位置,那么我希望你可以在阅读完这篇文章之后,你将对它们有更清晰的了解。
话虽如此,在这篇文章中,我列举了5个更好地理解的JavaScript的高阶函数的示例。希望对你理解高阶函数有帮助。
如果你仅是第一次使用JavaScript的话,就很可能已经通过本机JavaScript数组方法(例如.map)使用了高阶函数。
下面的代码段循环遍历数组,并在每个项目上调用一个函数,直到到达最后一个项目为止。 采取它可以调用的功能的能力是使其成为高阶函数的原因:
function prefixWordWithUnderscore(word) {
return `_${word}`
}
const words = ['coffee', 'apple', 'orange', 'phone', 'starbucks']
const prefixedWords = words.map(prefixWordWithUnderscore)
// result: ["_coffee", "_apple", "_orange", "_phone", "_starbucks"]
有时我们最终决定创建一个高阶函数,因此我们可以使用它来创建所需操作的更多变体。
在下面的示例中,基本的exploitPrefixer函数是一个高阶函数,该函数返回一个函数,该函数将文本作为参数并将前缀应用于要返回的值。
function utilizePrefixer(prefix) {
return function(word) {
return `${prefix}${word}`
}
}
const withMoneySign = utilizePrefixer('$')
const withCompanyName = utilizePrefixer('Fandango')
const tenDollars = withMoneySign('9.99')
const formHeader = withCompanyName(
' is the company you will be applying for today',
)
console.log(tenDollars)
console.log(formHeader)
每当你需要重构指向一个功能的一堆代码时,你都可以创建一个增强函数,该函数可以反转代码通过的方向。
我将复制并粘贴旧文章中的一个示例,因为这是一个很好的例子,可以说明这一点:
在我之前的工作中,我们使用react-toastify来显示通知。我们到处使用它。此外,它们还为紧急情况下的最新UX决策提供了很好的解决方法:“我们应如何处理此错误?只是显示了通知!”,然后就做完了。
但是,我们开始注意到当应用程序变大并且复杂性不断提高时,我们的通知变得频繁了。但是,我们没有防止重复的方法。这意味着一些通知在屏幕上多次显示,使它们与上方的通知内容完全相同。
因此,我们最终利用了该库提供的API,以帮助使用toast.dismiss()通过ID删除活动Toast通知。
为了解释前面的部分,在继续操作之前,先显示我们要从中导入文件可能是个好主意:
import React from 'react'
import { GoCheck, GoAlert } from 'react-icons/go'
import { FaInfoCircle } from 'react-icons/fa'
import { MdPriorityHigh } from 'react-icons/md'
import { toast } from 'react-toastify'
/*
Calling these toasts most likely happens in the UI 100% of the time.
So it is safe to render components/elements as toasts.
*/
// Keeping all the toast ids used throughout the app here so we can easily manage/update over time
// This used to show only one toast at a time so the user doesn't get spammed with toast popups
export const toastIds = {
// APP
internetOnline: 'internet-online',
internetOffline: 'internet-offline',
retryInternet: 'internet-retry',
}
// Note: this toast && is a conditional escape hatch for unit testing to avoid an error.
const getDefaultOptions = (options) => ({
position: toast && toast.POSITION.BOTTOM_RIGHT,
...options,
})
const Toast = ({ children, success, error, info, warning }) => {
let componentChildren
// Sometimes we are having an "object is not valid as a react child" error and children magically becomes an API error response, so we must use this fallback string
if (!React.isValidElement(children) && typeof children !== 'string') {
componentChildren = 'An error occurred'
} else {
componentChildren = children
}
let Icon = GoAlert
if (success) Icon = GoCheck
if (error) Icon = GoAlert
if (info) Icon = FaInfoCircle
if (warning) Icon = MdPriorityHigh
return (
{componentChildren}
)
}
export const success = (msg, opts) => {
return toast.success({msg} , {
className: 'toast-success',
...getDefaultOptions(),
...opts,
})
}
export const error = (msg, opts) => {
return toast.error({msg} , {
className: 'toast-error',
...getDefaultOptions(),
...opts,
})
}
export const info = (msg, opts) => {
return toast.info({msg} , {
className: 'toast-info',
...getDefaultOptions(),
...opts,
})
}
export const warn = (msg, opts) => {
return toast.warn({msg} , {
className: 'toast-warn',
...getDefaultOptions(),
...opts,
})
}
export const neutral = (msg, opts) => {
return toast({msg} , {
className: 'toast-default',
...getDefaultOptions(),
...opts,
})
}
现在,请耐心等待,我知道这可能看起来并不吸引人,但我保证会在两分钟内变好。
这是我们在一个单独的组件中用来检查屏幕上是否已经有以前的吐司的内容,如果有的话,它会尝试删除该吐司并重新显示新的吐司。
import { toast } from 'react-toastify'
import {
info as toastInfo,
success as toastSuccess,
toastIds,
} from 'util/toast'
const onOnline = () => {
if (toast.isActive(toastIds.internetOffline)) {
toast.dismiss(toastIds.internetOffline)
}
if (toast.isActive(toastIds.retryInternet)) {
toast.dismiss(toastIds.retryInternet)
}
if (!toast.isActive(toastIds.internetOnline)) {
toastSuccess('You are now reconnected to the internet.', {
position: 'bottom-center',
toastId: toastIds.internetOnline,
})
}
}
const onOffline = () => {
if (!toast.isActive(toastIds.internetOffline)) {
toastInfo('You are disconnected from the internet right now.', {
position: 'bottom-center',
autoClose: false,
toastId: toastIds.internetOffline,
})
}
}
useInternet({ onOnline, onOffline })
return
一切正常。但是,我们在整个应用程序中还有其他程序需要以相同的方式进行修改。 我们必须遍历所有显示通知的文件,以删除重复的文件。
当我们想到要在2019年检查每个文件时,我们立即知道这不是解决方案。 因此,我们查看了util / toast.js文件,并对其进行了重构以解决我们的问题。 如下所示:
src/util/toast.js
:
import React, { isValidElement } from 'react'
import isString from 'lodash/isString'
import isFunction from 'lodash/isFunction'
import { GoCheck, GoAlert } from 'react-icons/go'
import { FaInfoCircle } from 'react-icons/fa'
import { MdPriorityHigh } from 'react-icons/md'
import { toast } from 'react-toastify'
/*
Calling these toasts most likely happens in the UI 100% of the time.
So it is safe to render components/elements as toasts.
*/
// Keeping all the toast ids used throughout the app here so we can easily manage/update over time
// This used to show only one toast at a time so the user doesn't get spammed with toast popups
export const toastIds = {
// APP
internetOnline: 'internet-online',
internetOffline: 'internet-offline',
retryInternet: 'internet-retry',
}
// Note: this toast && is a conditional escape hatch for unit testing to avoid an error.
const getDefaultOptions = (options) => ({
position: toast && toast.POSITION.BOTTOM_RIGHT,
...options,
})
const Toast = ({ children, success, error, info, warning }) => {
let componentChildren
// Sometimes we are having an "object is not valid as a react child" error and children magically becomes an API error response, so we must use this fallback string
if (!isValidElement(children) && !isString(children)) {
componentChildren = 'An error occurred'
} else {
componentChildren = children
}
let Icon = GoAlert
if (success) Icon = GoCheck
if (error) Icon = GoAlert
if (info) Icon = FaInfoCircle
if (warning) Icon = MdPriorityHigh
return (
{componentChildren}
)
}
const toaster = (function() {
// Attempt to remove a duplicate toast if it is on the screen
const ensurePreviousToastIsRemoved = (toastId) => {
if (toastId) {
if (toast.isActive(toastId)) {
toast.dismiss(toastId)
}
}
}
// Try to get the toast id if provided from options
const attemptGetToastId = (msg, opts) => {
let toastId
if (opts && isString(opts.toastId)) {
toastId = opts.toastId
} else if (isString(msg)) {
// We'll just make the string the id by default if its a string
toastId = msg
}
return toastId
}
const handleToast = (type) => (msg, opts) => {
const toastFn = toast[type]
if (isFunction(toastFn)) {
const toastProps = {}
let className = ''
const additionalOptions = {}
const toastId = attemptGetToastId(msg, opts)
if (toastId) additionalOptions.toastId = toastId
// Makes sure that the previous toast is removed by using the id, if its still on the screen
ensurePreviousToastIsRemoved(toastId)
// Apply the type of toast and its props
switch (type) {
case 'success':
toastProps.success = true
className = 'toast-success'
break
case 'error':
toastProps.error = true
className = 'toast-error'
break
case 'info':
toastProps.info = true
className = 'toast-info'
break
case 'warn':
toastProps.warning = true
className - 'toast-warn'
break
case 'neutral':
toastProps.warning = true
className - 'toast-default'
break
default:
className = 'toast-default'
break
}
toastFn({msg} , {
className,
...getDefaultOptions(),
...opts,
...additionalOptions,
})
}
}
return {
success: handleToast('success'),
error: handleToast('error'),
info: handleToast('info'),
warn: handleToast('warn'),
neutral: handleToast('neutral'),
}
})()
export const success = toaster.success
export const error = toaster.error
export const info = toaster.info
export const warn = toaster.warn
export const neutral = toaster.neutral
不必遍历每个文件,最简单的解决方案是创建一个高阶函数。这样做使我们可以颠倒角色,因此与其一起搜索文件,不如将敬酒直接带给我们的高阶函数。
这样,文件中的代码就不会被修改或修改。所有这些仍然正常运行,并且我们获得了删除重复的吐司的能力,而无需最终写任何不必要的代码。这样节省了时间。
当你开始编写高阶函数时,它们会变得更加耀眼。人们可以出于多种原因选择作曲。对于下面的示例,由于要防止JavaScript对每个.filter
操作一遍又一遍地循环越过数组的原因,我们需要进行了组合:
function createFilterers() {
const _filters = {
ids: [],
fns: {},
}
return {
addFilter(name, fn) {
_filters.ids.push(name)
_filters.fns[name] = fn
},
removeFilter(name) {
const index = _filters.ids.indexOf(name)
if (index !== -1) _filters.splice(index, 1)
delete _filters.fns[name]
},
filter(arr) {
const filters = _filters.ids.map((id) => _filters.fns[id])
return arr.reduce((acc, item) => {
for (let index = 0; index < _filters.ids.length; index++) {
const id = _filters.ids[index]
const filter = _filters.fns[id]
if (!filter(item)) {
return acc
}
}
return acc.concat(item)
}, [])
},
}
}
const frogs = [
{
name: 'bobTheFrog',
age: 2,
gender: 'male',
favoriteFood: 'fly',
weight: 5,
},
{
name: 'lisaTheFrog',
age: 3,
gender: 'female',
favoriteFood: 'fly',
weight: 1,
},
{
name: 'sallyTheFrog',
age: 10,
gender: 'female',
favoriteFood: 'caterpillar',
weight: 20,
},
{
name: 'mikeTheFrog',
age: 1,
gender: 'male',
favoriteFood: 'worm',
weight: 8,
},
{
name: 'georgeTheFrog',
age: 7,
gender: 'male',
favoriteFood: 'fly',
weight: 28,
},
{
name: 'kellyTheFrog',
age: 3,
gender: 'female',
favoriteFood: 'ladybug',
weight: 3,
},
]
const filterer = createFilterers()
filterer.addFilter('fat-frogs', (frog) => {
return frog.weight >= 8
})
filterer.addFilter('male-frogs', (frog) => {
return frog.gender === 'male'
})
const filteredFrogs = filterer.filter(frogs)
console.log(filteredFrogs)
这样可以避免我们在应用中的任何地方都必须编写这样的重复代码:
const filteredFrogs = frogs
.filter((frog) => {
return frog.weight >= 8
})
.filter((frog) => {
return frog.gender === 'male'
})
.filter((frog) => {
return frog.name.startsWith('b')
})
值得一提的是,当我们创建一个高阶函数时,我们还可以在范围内创建一些本地状态,并将其缓存以备将来使用:
function createMyHigherOrderFunction(options) {
const state = { ...options } // Our local state object
return function(...args) {
return function(callback) {
return callback(state, ...args)
}
}
}
const prepare = createMyHigherOrderFunction({
name: 'bobby',
favoriteFood: 'steak',
})
const prepareWithArgs = prepare({ country: 'United States' })
const finalize = prepareWithArgs((state, options) => ({ ...state, ...options }))
console.log(finalize)
/*
result: {
country: "United States",
favoriteFood: "steak",
name: "bobby"
}
*/
到此结束。希望你发现这很有价值,并希望将来能有更多的机会!