# 函数柯里化

在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个 参数的函数的技术。

先看一道面试题:

add(1) //1
add(1)(2) //3
add(1, 2)(3) //6
1
2
3

解法如下

function add() {
    // 第一次执行时,定义一个数组专门用来储存所有的参数
    var args = [].slice.call(arguments)
    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    var fn = function () {
        var fn_args = [].slice.call(arguments)

        return add.apply(null, args.concat(fn_args))
    }
    // 利用隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    fn.toString = function () {
        return args.reduce(function (acc, prev) {
            return acc + prev
        })
    }

    return fn
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

基本思路就是利用闭包一层层的返回函数执行。本质上是只传递给函数一部分参数来调用 它,让它返回一个函数去处理剩下的参数。返回来说就是用单参数的函数拼接成一个多 参数的函数。在柯里化的应用场景中,比如add(1)(2)(3)这样的代码可以重构 为let add1 = add(1);let add2 = add1(2);let add3 = add2(3) 分段执行。这样做的好 处在于参数复用,复用每个阶段的参数。

柯里化重要意义在于可以把函数完全变成「接受一个参数;返回一个值」的固定形式。当然 在函数式语言中会自动柯里化,但 JS 中柯里化是一个类似告诫函数的概念,bind函数的 实现就有点柯里化的意思。

回到函数柯里化的定义中,如何将多个参数的一个函数转换为使用一个参数的函数?

function add(a, b) {
    return a + b
}
// 执行 add 函数,一次传入两个参数即可
add(1, 2) // 3

// 假设有一个 curry 函数可以做到柯里化
var addCurry = curry(add)
addCurry(1)(2) // 3

// curry函数
function sub_curry(fn) {
    var args = [].slice.call(arguments, 1)
    return function () {
        return fn.apply(this, args.concat([].slice.call(arguments)))
    }
}

function curry(fn, length) {
    length = length || fn.length

    var slice = Array.prototype.slice

    return function () {
        if (arguments.length < length) {
            var combined = [fn].concat(slice.call(arguments))
            return curry(
                sub_curry.apply(this, combined),
                length - arguments.length
            )
        } else {
            return fn.apply(this, arguments)
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

无论是柯里化还是偏函数,我们都能进行部分传值,而传统函数调用则需要预先确定所有实 参。如果你在代码某一处只获取了部分实参,然后在另一处确定另一部分实参,这个时候柯 里化和偏函数就能派上用场。另一个最能体现柯里化应用的的是,当函数只有一个形参时, 我们能够比较容易地组合它们(单一职责原则(Single responsibility principle))。 因此,如果一个函数最终需要三个实参,那么它被柯里化以后会变成需要三次调用,每次调 用需要一个实参的函数。当我们组合函数时,这种单元函数的形式会让我们处理起来更简单 。

归纳下来,主要为以下常见的三个用途:

  • 延迟计算
  • 参数复用
  • 动态生成函数