JavaScript中的函数柯里化(Currying)
柯里化(Currying)是一种函数式编程的技术,其主要思想是将一个多参数的函数转换成一系列单参数的函数。这使得我们可以逐步传递参数,每次传递一个参数,返回一个新的函数,直到所有参数都被收集完毕并执行原始函数。这种技术的名字来源于数学家 Haskell Curry。
柯里化不会调用函数。它只是对函数进行转换。
高阶函数
柯里化函数是高阶函数的一种特殊应用。高阶函数是指能够接受一个或多个函数作为参数,并且/或者返回一个新函数的函数。在JavaScript中,函数是第一类对象,这使得高阶函数成为一种常见的编程模式。
接受函数作为参数
高阶函数可以接受一个或多个函数作为参数。这样的函数通常用于执行传入的函数,或者基于传入的函数执行一些逻辑。
1 | function operateOnArray(array, operation) { |
返回函数(闭包)
高阶函数可以返回一个新的函数。 这样的函数通常用于封装、延迟执行或者组合其他函数。
1 | function multiplyBy(factor) { |
函数组合
高阶函数可以将多个函数组合在一起形成新的函数。 这样的函数组合可以提高代码的可读性和复用性。
1 | function compose(f, g) { |
回调函数
高阶函数常常用于处理异步操作,通过回调函数来执行一些逻辑。
1 | function fetchData(callback) { |
函数柯里化
柯里化是一种将接受多个参数的函数转化为一系列接受一个参数的函数的技术。这可以通过高阶函数来实现。
1 | function curry(fn) { |
柯里化的优点
参数复用
柯里化允许我们部分应用函数并在之后的调用中重复使用这部分应用的函数。这对于在不同上下文中多次使用相同的参数集合非常有用。
1 | // 普通加法函数 |
在上面的例子中,我们部分应用了加法函数,并创建了一个新的函数add2
,它在之后的调用中一直使用参数2
。
延迟执行
柯里化使得我们可以逐步传递参数,延迟函数的执行。这对于需要等待所有参数就绪的场景非常有用。
1 | // 普通乘法函数 |
在上面的例子中,我们逐步传递参数,最终在第三步中执行乘法函数。
函数组合
柯里化方便了函数的组合,可以将多个单参数函数组合成一个函数链。
1 | // 普通函数 |
在上面的例子中,我们使用柯里化创建了两个单参数函数,并将它们组合成了一个函数链。
柯里化的缺点
- 可能会降低性能(但跟操作DOM相比可以忽略不计) 。通过柯里化,函数的性能可能会降低,因为需要额外的内存来存储函数的返回值和参数。
- 可能会增加代码复杂度。通过柯里化,可能会增加代码的复杂度,因为需要处理额外的参数和函数返回值。
柯里化的实现
普通实现
普通实现就是把一个接受多参数的函数变成每次直接收一个参数的函数,其中用到了闭包和高阶函数的知识。
也可以使用lodash 库的 _.curry
函数实现柯里化。
1 | function curry(f) { // curry(f) 执行柯里化转换 |
正如上面看到的,实现非常简单:只有两个包装器(wrapper)。
curry(func)
的结果就是一个包装器function(a)
。- 当它被像
curriedSum(1)
这样调用时,它的参数会被保存在词法环境中,然后返回一个新的包装器function(b)
。
然后这个包装器被以 2 为参数调用,并且,它将该调用传递给原始的 sum
函数。
柯里化更高级的实现,例如 lodash 库的 _.curry,会返回一个包装器,该包装器允许函数被正常调用或者以偏函数(partial)的方式调用:
1 | function sum(a, b) { |
通用的函数柯里化
实现原理:
将一个函数
fn
传入进行柯里化,返回一个curried
的函数(已经柯里化好了)对
curried
传参:- 如果传的参数个数$\ge$原函数
fn
的参数个数即fn.length
(arity参数数量):返回fn(参数)
的执行结果。 - 否则,如果传的参数个数$\lt$原函数
fn
的参数个数即fn.length
,递归地进行柯里化curried
,接受新的参数与之前的参数合并(可以使用剩余参数),直到参数个数达到了原函数fn
的参数个数即fn.length
,到达递归出口。
- 如果传的参数个数$\ge$原函数
1 | // 柯里化函数 |
使用看看:
1 | const sum = (a, b, c)=> a+b+c |