函数科里化总结

407 阅读2分钟

柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

1. 实现一个通过函数科里化相加的函数

function add(){
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    const _args = [...arguments]
    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    function fn() {
        _args.push(...arguments)
        return fn
    }
    // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    fn.toString = function(){
        return _args.reduce((p,n)=>p+n)
    }
    return fn
}

// 以下结果都返回6
add(1,2,3)
add(1)(2,3)
add(1)(2)(3)
add(1)()(2,3)
add(1)()(2,3)()()

2. 实现函数相加相乘相除的简便操作

var curry = function(fn) {
    var _args = []
    return function cb() {
        if (arguments.length == 0) {
            return fn.apply(this, _args)
        }
        Array.prototype.push.apply(_args, arguments);
        return cb;
    }
}
var sum = function(...rest) {
    return rest.reduce((p,n)=>{
        return p+n
    })
}
var multiply = function(...rest) {
    return rest.reduce((p,n)=>{
        return p*n
    })
}
var divide = function(...rest) {
    return rest.reduce((p,n)=>{
        return p/n
    })
}
var sumc = curry(sum);
let multiplyc = curry(multiply)
let dividec = curry(divide)
console.log(sumc(1,5)(2)(3)(4)()); // 15
console.log(multiplyc(1,5)(2)(3)(4)(5)()); // 600
console.log(dividec(120, 2)(2, 2)(1)()); // 15

3. curry的性能和好处

1.. curry的一些性能问题你只要知道下面四点就差不多了:

  • 存取arguments对象通常要比存取命名参数要慢一点
  • 一些老版本的浏览器在arguments.length的实现上是相当慢的
  • 使用fn.apply( … ) 和 fn.call( … )通常比直接调用fn( … ) 稍微慢点
  • 创建大量嵌套作用域和闭包函数会带来花销,无论是在内存还是速度上

其实在大部分应用中,主要的性能瓶颈是在操作DOM节点上,这js的性能损耗基本是可以忽略不计的,所以curry是可以直接放心的使用。

2. curry的好处

  1. 参数复用: 需要输入多个参数,最终只需输入一个,其余通过arguments来获取
  2. 提前确认: 避免重复去判断某一条件是否符合,不符合则return 不再继续执行下面的操作
  3. 延迟执行: 避免重复的去执行程序,等真正需要结果的时候再执行

4. 例子

1. 假设我们要编写一个计算每月开销的函数。在每天结束之前,我们都要记录今天花掉了多少钱。 但是只有当一个月结束的时候, 我们才需要计算总共花了多少钱。 这时候就可以使用科里化进行延迟执行

let currying = function(fn) {
    let args = [];
    function innerFn(){
        if (arguments.length === 0) {
            return fn.apply(this, args);
        } else {
            [].push.apply(args, arguments);
            return innerFn;
        }
    }
    return innerFn
};

let fnCost = (function() {
    let money = 0;
    return function() {
        for (let i = 0, l = arguments.length; i < l; i++) {
            money += arguments[i];
        }
        return money;
    }
})();

let cost = currying(fnCost); // 转化成 currying 函数
cost(100); // 未真正求值
cost(200); // 未真正求值 
console.log(cost(300)); // 未真正求值 返回 [Function: innerFn]
console.log(cost()); // 求值并输出:600