高阶函数

JavaScript中的高阶函数(Higher-Order Functions)

什么是高阶函数

是指能够接受一个或多个函数 作为参数,并/或者返回一个新函数的函数。

具体来说,高阶函数具备以下两个特点之一或两者兼具:

  1. 接受函数作为参数:高阶函数可以接受一个或多个函数作为参数,这些函数可以在高阶函数内部被调用、处理或者组合,以实现特定的功能。
1
2
3
4
5
6
7
8
9
10
11
function higherOrderFunction(callback) {
// todo...
callback();
}

function callbackFunction() {
console.log('这是回调函数');
}

higherOrderFunction(callbackFunction); // 输出:这是回调函数

  1. 返回一个新函数:高阶函数可以根据内部逻辑动态地生成并返回一个新的函数,该函数可以在其他地方被调用和使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createGreeter(greeting) {
return function(name) {
// todo...
console.log(`${greeting}, ${name}!`);
};
}

const greetHello = createGreeter('Hello');
greetHello('Alice'); // 输出:Hello, Alice!
greetHello('Bob'); // 输出:Hello, Bob!

const greetHi = createGreeter('Hi');
greetHi('Charlie'); // 输出:Hi, Charlie!

高阶函数的作用

1. 对原有函数进行扩展 (AOP切片编程)

假如有一个 core 函数,在不修改这个函数的情况下对 core 函数进行封装

1
2
3
4
5
6
7
8
9
10
// 我们希望对这个core进行封装
function core(a,b,c){
console.log('核心逻辑',a,b,c)
}
// 希望增添一个before函数来扩展
const newCore = core.before(() => {
console.log('增加的额外逻辑')
})
// 能调用,说明返回值还是一个函数
newCore(1,2,3)

这就需要再 core 函数上扩展一个 before 方法

1
2
3
4
5
6
7
8
9
10
// Function.prototype.before = 可以扩展到函数原型上,所有函数都可调用
core.before = function(fn){
// 返回的是一个函数
return (...args) => {
// todo...
fn() // 在核心逻辑之前执行自己的函数(逻辑)
this(...args) // AOP 切片增加额外的逻辑,在原有的逻辑中增添额外的逻辑
// todo...
}
}

2. 函数 柯里化偏函数

函数的柯里化(Currying)是一种将接受多个参数的函数转化为一系列接受单个参数的函数的过程

具体来说,柯里化将一个接受多个参数的函数转化为一系列只接受一个参数的函数。每个单参数函数都会返回一个新的函数,该新函数接受下一个参数,并继续返回一个新函数,直到所有参数都被处理完毕,最后返回最终的结果。

而偏函数 (参数可以不是一个的柯理化函数)

正常来编写代码 我们把偏函数也称之为柯理化

简单的柯里化示例

1
2
3
4
5
6
7
8
9
10
11
12
function add(x) {
return y => {
return x + y;
};
}

const add5 = add(5); // 部分应用,返回一个新函数
console.log(add5(3)); // 输出:8

const add10 = add(10);
console.log(add10(3)); // 输出:13

让我们通过一个例子来说明柯里化的好处

判断某个变量是不是某个类型我们会这样写

1
2
3
4
5
6
7
function isType(val,typing) {
return Object.prototype.toString.call(val).slice(8,-1) === typing
}

// 判断某个变量是不是一个字符串
console.log(isType('hello','String'))
console.log(isType('abc','String'))

typing类型每次需要传递,有些多余,我们会想到用高阶函数把它缓存起来,这样更方便

1
2
3
4
5
6
7
8
9
10
function isType(typing) {
// typing 保存到这个作用域下 (闭包)
return val => { // 定义的作用域
return Object.prototype.toString.call(val).slice(8,-1) === typing
}
}

let isString = isType('String') // 闭包 定义的函数的作用域和执行函数的作用域不是同一个就是产生闭包
console.log(isString('abc')) // 执行的作用域
console.log(isString('c'))

然而有了柯理化函数,我们也可以这样

1
2
3
4
5
6
7
8
function isType(val,typing) {
return Object.prototype.toString.call(val).slice(8,-1) === typing
}

// 判断某个变量是不是一个字符串
let isString = curry(isType)('String')
console.log(isString(123))
console.log(isString('abc'))

那么关键问题来了,这个柯里化函数curry是怎么实现的,他做了那些事情呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 判断参数个数是否等价
function sum(a,b,c){
return a + b + c
}
// sum(1,2)(3)

// 柯理化函数一定是高阶函数
function curry(func){
const curried = (...args) => { // 用户本次执行的时候传递的参数,本次为 1,2
// 这里判断参数的长度是否等于需要柯理化的函数的参数长度 ps: 关键问题的关键QAQ
if(args.length < func.length){ // ps: 函数的长度等于参数的个数
return (...others) => curried(...args,...others)
}else{
return func(...args)
}
}
return curried

}
let curriedSum = curry(sum)
console.log(curriedSum(1,2)(3)) // 6
console.log(curriedSum(1)(2)(3)) // 6

函数的柯里化可以带来一些好处,例如:

  • 提供更高的灵活性和复用性:通过柯里化,我们可以轻松地创建一系列根据不同情况预设部分参数的函数,以适应不同的使用场景。
  • 实现函数的延迟执行:柯里化可以将一个多参数函数转化为一系列接受单个参数的函数,这样可以延迟函数的执行,只有当所有参数都准备就绪时才真正执行。
  • 支持函数组合:柯里化可以方便地将多个函数组合在一起,形成一个新的函数管道,使代码更加模块化和可组合。

总结

高阶函数在JavaScript中具有广泛的应用,可以用于实现函数的组合、函数的延迟执行、函数的柯里化等功能。常见的高阶函数包括map、filter、reduce等数组方法,以及setTimeout、setInterval等定时器函数。

通过使用高阶函数,我们可以更灵活地处理函数和数据,使代码更加简洁、可复用和可扩展。