JS函数柯里化

512 阅读4分钟
1.定义

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

2.释义

function sum(a,b,c){
    return a+b+c;
}
function sumCurry(a){
    return function(b){
        return function(c){
            return a+b+c;
        }
    }
}

存在一个一般求和函数:sum,函数调用时需将sum所需的参数一次性传入,即sum(1,2,3),返回求和的结果。而经过柯里化的函数则允许一次传递一个参数,即sumCurry(1)(2)(3),直到参数全部传入后再返回最终的执行结果。

在这里只需要用js闭包的思想去理解就可以,最后返回的function(c){}中可以访问sumCurry中的变量a,以及function(b){}中的变量b。

3.用例

(1)存储变量,方便复用

function styles(color,fontSize){
    return {
        color:color,
        fontSize:fontSize
    }
}
var style1 = styles('red',13);//{color:'red',fontSize:13}
var style2 = styles('red',14);//{color:'red',fontSize:14}
var style3 = styles('green',13);//{color:'green',fontSize:13}
var style4 = styles('green',14);//{color:'green',fontSize:14}

每次生成一个新的样式都需要传递两个参数,color和fontSize。若在一个页面中,样式的color不变,只是fontSize变化,则每次都需要传递两个参数,略显麻烦。

function stylesCurry(color){
    return function(fontSize){
        return {
            color:color,
            fontSize:fontSize
        }
    }
}
var redStyle = stylesCurry('red');
var greenStyle = stylesCurry('green');
var style1 = redStyle(13);//{color:'red',fontSize:13}
var style2 = redStyle(14);//{color:'red',fontSize:14}
var style3 = greenStyle(13);//{color:'green',fontSize:13}
var style4 = greenStyle(14);//{color:'green',fontSize:14}

函数经过柯里化之后,保存了color变量的值,方便了相同color值的复用。存储变量的常见用法,如存储正则表达式,方便复用。

(2)提前返回

var addEvent = function(el, type, fn, capture) {
    if (window.addEventListener) {
        el.addEventListener(type, function(e) {
            fn.call(el, e);
        }, capture);
    } else if (window.attachEvent) {
        el.attachEvent("on" + type, function(e) {
            fn.call(el, e);
        });
    } 
};

每次调用addEvent都要进行if-else判断

var addEvent = (function(){
    if (window.addEventListener) {
        return function(el, sType, fn, capture) {
            el.addEventListener(sType, function(e) {
                fn.call(el, e);
            }, (capture));
        };
    } else if (window.attachEvent) {
        return function(el, sType, fn, capture) {
            el.attachEvent("on" + sType, function(e) {
                fn.call(el, e);
            });
        };
    }
})();

而addEvent经过柯里化之后,返回了一个确定的结果,再次调用addEvent时不会再进行if-else的判断。

(3)延迟执行

var money = 0;
function total(m){
    money = money+m;
    return money;
}
total(1); //1
total(2); //3
total(3); //6

每一次调用total函数都会进行一次计算。

function curry(fn){
    var _args = [];
    return function(){
        if(arguments.length===0){
            return fn.apply(null,_args);
        }else{
            _args = _args.concat([].slice.apply(arguments));
        }
    }
}
var money = 0;
var totalCurry = curry(function(){
    for(var i=0;i<arguments.length;i++){
        money = money+arguments[i];
    }
    return money;
})
totalCurry(1);
totalCurry(2);
totalCurry(3);
totalCurry();//6 到这里计算

4.通用柯里化函数

常见形式的通用柯里化函数:

var s = 0;
function sum(){
    for(var i=0;i<arguments.length;i++){
        s = s+arguments[i];
    }
    return s;
}
var currying = function(fn) {
    // args 获取第一个方法内的全部参数
    var args = Array.prototype.slice.call(arguments, 1)
    return function() {
        // 将后面方法里的全部参数和args进行合并
        var newArgs = args.concat(Array.prototype.slice.call(arguments))
        // 把合并后的参数通过apply作为fn的参数并执行
        return fn.apply(this, newArgs)
    }
}
var _sum = currying(sum);
_sum(1,2);//3
_sum(4,5);//12 (3+4+5)

该方式的柯里化函数存在缺陷,不支持_sum(1)(2)这种调用方式

优化版:

function sum(a,b,c){
    return a+b+c;
}
var curry = function(fn,args){
    //将数组的slice方法保存下来方便其他类数组对象调用
    var  slice = Array.prototype.slice;
    //fn函数接收的参数个数
    var len = fn.length;
    //将fn以外的其他参数保存起来
    var _args = args ? slice.call(args) : [];
    return function () {
        //将以前存储的参数与新传递的参数结合起来
        var argsNew = _args.concat(slice.call(arguments));
        //参数数量大于fn需要的参数数量时执行函数,否则继续存储参数
        if (argsNew.length >= len) {
            return fn.apply(this, argsNew);
        } else {
            return curry.call(this, fn, ...argsNew);
        }
    }
}
var _sum = curry(sum);
_sum(1)(2)(3); //6
_sum(1,2,3); //6
_sum(1,2)(3); //6

该通用柯里化函数仅适用于参数数量确定的函数。

5.无限参数的柯里化

常见面试题:实现一个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 () {
                [].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));
    }
add(1)(2)(3);//6
add(1)(2)(3)(4);//10
add(1,2,3,4)(5);//15

6.参考链接

www.zhangxinxu.com/wordpress/2…

segmentfault.com/a/119000001…