面试官让我库里(Curry)化一个函数,库里?篮球?函数?
面试官: 同学你好,请库里化一个函数
我: 库里不是打篮球的嘛?
面试官笑了笑: 是柯里化哦
我:这是个啥?内心再次慌的一批。。。
前言
柯里化(Currying)是函数式编程中的一种技术,它将一个接受多个参数的函数转换成一系列每次接受一个参数的函数。换句话说,柯里化后的函数只接受一个参数,并返回一个新的函数,该函数再接受下一个参数,以此类推,直到所有参数都被处理完为止。
在此之前,让我们先来了解一些必备知识。
简单的两数相加
我们看这个两数相加的函数,可以发现是比较严谨的,我们既对传入值的类型作了判断,还对传入参数作了判断,二者必须同时满足才能正确执行。
简单的闭包
我们采用闭包的方式让我们的参数可以传递两次,首先我们返回函数体,函数体返回的是上一个函数的参数值和此函数里的参数值,我们就实现了连续传两次参数来实现相加的目的。
认识...rest和arguments
首先我们要知道,函数里面都有一个arguments,表示的是传入函数的数值数组(伪数组),但是基本我们要用到的功能它都具有。而剩余参数也是一个数组,我们可以用它来接收传入函数参数里多余的参数。但是我们要铭记一点,就是因为语法的更新,箭头函数里已经不再支持使用arguments了。
function sum(a, b, c) {
console.log(arguments);
}
sum(1, 2, 3)
sum(1, 2)
function sum1(a, ...args) {
console.log(args);
}
sum1(1, 2, 3)
sum1(1)
//[Arguments] { '0': 1, '1': 2, '2': 3 }
//[Arguments] { '0': 1, '1': 2 }
//[ 2, 3 ]
//[]
函数的柯里化
我们接下直接摆出这段代码,大家第一次看的时候可能会有点不懂。让我为大家详细解释一下。首先我们要知道柯里化一个函数的目的是实现参数的多次传递,传满再进行计算。比如我们的函数是实现四个数的相加。那么有没有一种方法,让我们可以多次传值,传满再进行计算呢?我们定义一个curry函数,也就是我听错的库里函数。函数放参数第一位,然后用剩余参数接收。后面是一个三元表达式,首先我们要知道函数的长度指的是什么。
function sum(a, b, c, d) {
console.log(sum.length);
}
sum(1)
sum(1, 2, 3, 4)
//4
//4
这里我们可以发现指的就是形参个数。当我们传入参数数量大于等于它时,直接调用函数进行计算。否则我们返回的仍然是一个函数。这里我们是一个匿名函数里头返回curry函数,采用了闭包的形式。比如我们传入1时,发现参数小于4,这个时候返回
function (..._args) {
return curry(fn, ...args, ..._args);
}
这样子的匿名函数,里头的...args是我们的1.
然后再传值2,这个时候我们的返回类似
function(..._args) {
return curry(add, 1, 2, ..._args);
}
详解如下:
使用 curry(add, 1, 2)
:
-
调用
curry(add, 1, 2)
:fn
是add
。args
是[1, 2]
。
-
检查
args.length
(2)是否大于或等于fn.length
(4,即add
函数的参数个数)。- 因为
2 < 4
,所以返回一个新的函数:
- 因为
(..._args) => curry(add, 1, 2, ..._args)
这个新的函数还没有被调用,因此 curry(add, 1, 2)
返回的是一个新的函数,而不是最终的结果。
使用 curry(add)(1)(2)
:
-
调用
curry(add)
:fn
是add
。args
是[]
。
-
检查
args.length
(0)是否大于或等于fn.length
(4)。- 因为
0 < 4
,所以返回一个新的函数:
- 因为
(..._args) => curry(add, ..._args)
- 再调用这个新的函数并传递
1
:
curry(add)(1)
这会导致再次调用 curry
:
curry(add, 1)
-
此时:
fn
是add
。args
是[1]
。
-
再检查
args.length
(1)是否大于或等于fn.length
(4)。- 因为
1 < 4
,所以返回一个新的函数:
- 因为
(..._args) => curry(add, 1, ..._args)
- 最后,再调用这个新的函数并传递
2
:
curry(add)(1)(2)
这会导致再次调用 curry
:
curry(add, 1, 2)
-
此时:
fn
是add
。args
是[1, 2]
。
-
再检查
args.length
(2)是否大于或等于fn.length
(4)。- 因为
2 < 4
,所以返回一个新的函数:
- 因为
(..._args) => curry(add, 1, 2, ..._args)
结论
匿名函数 (..._args) => curry(fn, ...args, ..._args)
不等同于直接返回 curry(fn, ...args, ..._args)
。匿名函数必须被调用后才会执行 curry(fn, ...args, ..._args)
,因此它实际上是一个新的函数,需要进一步调用才能继续处理剩余的参数。这就是柯里化的本质:逐步收集参数,直到收集到足够的参数,然后执行原始函数。
我们来简单打印一下
function add(a, b, c, d) {
return a + b + c + d
}
const curry = (fn, ...args) => args.length >= fn.length ? fn(...args) : (..._args) => curry(fn, ...args, ..._args)
console.log(curry(add, 1));
console.log(curry(add, 1)(2));
console.log(curry(add, 1, 2)(3, 4));
//[Function (anonymous)]
//[Function (anonymous)]
//10
我们从这里可以非常清晰地看见我们的结果!
转载自:https://juejin.cn/post/7379509948903178291