函数科里化
文章目录

函数科里化

科里化的目的:

  1. 模块化
  2. 降低耦合
  3. 增加可维护性
  4. 复用 & 延迟运行
  5. 提前确认

看一个典型的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 普通的add函数
function add(x, y{
    return x + y
}

// Currying后
function curryingAdd(x{
    return function (y{
        return x + y
    }
}

add(12// 3
curryingAdd(1)(2// 3

复用以及延迟运行

1
2
3
4
5
// 可以提前获得一个函数随后使用
let funcPlusOne = curryingAdd(1);
...
...
funcPlusOne(4// 5

提前确认

1
2
3
4
5
6
7
8
9
10
11
// Currying后
function curryingAdd(x{
    if (x > 5
        return function (y){
            return x - y
        }
    else
        return function (y{
            return x + y
        }
}

这里可以对第一个参数进行一些判断,然后决定后面要执行的逻辑。

通用的科里化封装方法

初步封装, 仅接受两次参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var currying = function(fn{
    // args 获取第一个方法内的全部参数
    var args = Array.prototype.slice.call(arguments1)

    return function({
        // 将后面方法里的全部参数和args进行合并
        var newArgs = args.concat(Array.prototype.slice.call(arguments))
        // 把合并后的参数通过apply作为fn的参数并执行
        return fn.apply(this, newArgs)
    }
}

function add(x, y{
    return x + y
}

currying(add)(1,2); //3
// add 方法传进去之后延迟了执行, 等到 (1,2) 传进来之后才执行 fn.apply

但是好像还有些什么缺陷,这样返回的话其实只能多扩展一个参数,currying(a)(b)(c)这样的话,貌似就不支持了(不支持多参数调用),一般这种情况都会想到使用递归再进行封装一层。

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
function progressCurrying(fn, args{

    var _this = this
    var len = fn.length; // 这个地方是获取 fn 参数个数, 如果用 add 方法那么就是 2
    var args = args || []; // 这个保存已经接收的参数列表

    return function({
        var _args = Array.prototype.slice.call(arguments);
        Array.prototype.push.apply(args, _args);
        // 新接收的参数列表会被传到 args 里面
        // 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
        // 对于 add 方法, 如果一次只供了一个参数,那么就会再调用一次
        if (args.length < len) {
            return progressCurrying.call(_this, fn, _args);
        }

        // 直到接收的参数个数为两个的时候。
        // 参数收集完毕,则执行fn
        return fn.apply(this, args);
    }
}

function add(x, y{
    return x + y
}

progressCurrying(add)(1)(2); //3

var funcPlusTwo = progressCurrying(add)(2);
funcPlusTwo(4//6

简单例子 (待验证)

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
36
// 实现一个add方法,使计算结果能够满足如下预期:
// add(1)(2)(3) = 6
// add(1, 2, 3)(4) = 10
// add(1)(2)(3)(4)(5) = 15


function add({
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = [].slice.call(arguments);

    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    var adder = function ({
        var _adder = function({
            // 每次都将参数保存找 _args
            [].push.apply(_args, [].slice.call(arguments));
            return _adder;
        };

        // 利用隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
        _adder.toString = function ({
            return _args.reduce(function (a, b{
                return a + b;
            });
        }

        // 递归到最后返回的依然是一个函数
        return _adder;
    }
    
    // 开始递归
    return adder.apply(null, [].slice.call(arguments));
}

console.log(add(12345));  // 15
console.log(add(1234)(5));  // 15
console.log(add(1)(2)(3)(4)(5));  // 15