前言
本文主要从 3W (what, how, why) 角度出发通俗易懂地解释一下 什么是函数柯里化,以及怎么用三行代码来实现 add(1)(2)(3) 这是个很常见的面试题。
什么是函数柯里化(curry)
函数柯里化(curry)是函数式编程里面的概念。curry的概念很简单:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
简单点来说就是:每次调用函数时,它只接受一部分参数,并返回一个函数,直到传递所有参数为止。
举个
将下面接受两个参数的函数改为接受一个参数的函数。
const add = (x, y) => x + y;
add(1, 2);
改成每次只接受一个参数的函数
const add = x => y => x + y;
add(1)(2);
柯里化,不可变数据类型,纯函数等都是函数式编程中的概念。在React中这些概念很常见,因为React中很多涉及到函数式编程的概念。想要具体了解什么是函数式编程,可以查看 JS函数式编程指南
add(1)(2)(3)
我们可以自己先尝试写一个add(1)(2)(3)
const add = x => y => z => x + y + z;
console.log(add(1)(2)(3));
看起来并不是那么难,但是如果面试官的要求是实现一个add 函数,同时支持下面这几种的用法呢
add(1, 2, 3);
add(1, 2)(3);
add(1)(2, 3);
如果还是按照上面的这种思路,我们是不是要写很多种呢...
我们当然可以自己实现一个工具函数专门来生成 柯里化 函数。
主要思路是什么呢,要判断当前传入函数的参数个数 (args.length) 是否大于等于原函数所需参数个数 (fn.length) ,如果是,则执行当前函数;如果是小于,则返回一个函数。
const curry = (fn, ...args) =>
// 函数的参数个数可以直接通过函数数的.length属性来访问
args.length >= fn.length // 这个判断很关键!!!
// 传入的参数大于等于原始函数fn的参数个数,则直接执行该函数
? fn(...args)
/**
* 传入的参数小于原始函数fn的参数个数时
* 则继续对当前函数进行柯里化,返回一个接受所有参数(当前参数和剩余参数) 的函数
*/
: (..._args) => curry(fn, ...args, ..._args);
function add1(x, y, z) {
return x + y + z;
}
const add = curry(add1);
console.log(add(1, 2, 3));
console.log(add(1)(2)(3));
console.log(add(1, 2)(3));
console.log(add(1)(2, 3));
Ramda
Ramda 中的函数所有都支持柯里化。也就是说,所有的多参数函数,默认都可以使用单参数函数。
还是举上面的例子吧
const addThreeNumbers = (x, y, z) => x + y + z;
const curriedAddaddThreeNumbers = R.curry(addThreeNumbers);
const f = curriedAddaddThreeNumbers(1, 2);
console.log(f(3));
大名鼎鼎的 lodash 中也提供了 柯里化 函数 ,那么它和Ramda有什么区别呢
lodash是一个很强大的工具函数库,比如 节流,防抖,深拷贝等等,只要引入 lodash ,我们就可以直接使用。
Ramda 是一个函数式编程的理念的函数库。
柯里化有什么作用
主要有3个作用: 参数复用、提前返回和 延迟执行
我们来简单地解释一下:
参数复用:拿上面 f这个函数举例,只要传入一个参数 z,执行,计算结果就是 1 + 2 + z 的结果,1 和 2 这两个参数就可以直接复用了。
提前返回 和 延迟执行 也很好理解,因为每次调用函数时,它只接受一部分参数,并返回一个函数(提前返回),直到(延迟执行)传递所有参数为止。
考点:函数柯里化
函数柯里化概念: 柯里化(Currying)是把接受多个参数的函数转变为接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术。
1)粗暴版
function add (a) {
return function (b) {
return function (c) {
return a + b + c;
}
}
}
console.log(add(1)(2)(3)); // 6
2)柯里化解决方案
- 参数长度固定
const curry = (fn) =>
(judge = (...args) =>
args.length === fn.length
? fn(...args)
: (...arg) => judge(...args, ...arg));
const add = (a, b, c) => a + b + c;
const curryAdd = curry(add);
console.log(curryAdd(1)(2)(3)); // 6
console.log(curryAdd(1, 2)(3)); // 6
console.log(curryAdd(1)(2, 3)); // 6
- 参数长度不固定
function add (...args) {
//求和
return args.reduce((a, b) => a + b)
}
function currying (fn) {
let args = []
return function temp (...newArgs) {
if (newArgs.length) {
args = [
...args,
...newArgs
]
return temp
} else {
let val = fn.apply(this, args)
args = [] //保证再次调用时清空
return val
}
}
}
let addCurry = currying(add)
console.log(addCurry(1)(2)(3)(4, 5)()) //15
console.log(addCurry(1)(2)(3, 4, 5)()) //15
console.log(addCurry(1)(2, 3, 4, 5)()) //15