js中的柯里化

维基百科中的解释:

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

顾名思义,柯里化其实本身是固定一个可以预期的参数,并返回一个特定的函数,处理批特定的需求。这增加了函数的适用性,但同时也降低了函数的适用范围。

柯里化所要表达是:如果你固定某些参数,你将得到接受余下参数的一个函数。

柯里化实现的通用版本

var curry = function(func){
    //在此处arguments的第一个参数是func(),第二个参数是调用curry时传入的参数,所以获取args时要从1处开始取
    var args = [].slice.call(arguments,1);
    return function(){
        //此处的arguments是使用curry函数的函数传入的参数
        var newArgs = args.concat([].slice.call(arguments));
        return func.apply(this,newArgs);
    }
}
  • 首先将参数进行分割,也就是将除了func之外的参数存进args。
  • 返回的函数接受新传入的参数并与之前的参数合并,从而将所有的参数传入函数中,并执行真正的函数。

一个例子

function add(a, b) {
    return a + b;
}

var addCurry = curry(add,1,2);
addCurry(); //3

//或者
var addCurry = curry(add,1);
addCurry(2); //3

//或者
var addCurry = curry(add);
addCurry(1, 2) // 3

 

柯里化的另一个例子

 1     var currying = function(fn) {
 2         console.log([].slice.call(arguments,0));    //结果是fn()、“参数1”,这里的参数是因为getArg函数调用了currying,并传入了参数“参数1”,即使没有第23行,此处的参数也是有的
 3         //对于currying来说,它的第一个参数是fn(),后面的才是传入的参数,所以要截取传入的参数要从1开始
 4         var args = [].slice.call(arguments, 1);
 5         return function() {
 6             //这里实际是currying这个函数主体,所以这里的arguments就是第23行调用getArg()函数时传入参数
 7             console.log([].slice.call(arguments,0));    
 8             // 已经有的参数和新的参数合成一体
 9             var newArgs = args.concat([].slice.call(arguments));
10             // 这些参数用 fn 这个函数消化利用,并返回
11             return fn.apply(null, newArgs);
12             //return fn.apply(null,[1,2,3]);        //如果是这样的话,那么第17行的arguments就是1,2,3
13         };
14     };
15     var getArg = currying(function() {
16         //这里的arguments其实就是上面fn传下来的参数
17         console.log([].slice.call(arguments,0));
18         var allArg = [].slice.call(arguments);
19         // allArg 就是所有的参数
20         console.log(allArg.join(";"));
21     }, "参数1");
22     // 获得其他参数
23     getArg("参数2","参数3","参数4","参数5","参数6","参数7");

柯里化的作用

1、参数复用:上面的例子

2、提前返回

很常见的一个例子,兼容现代浏览器以及IE浏览器的事件添加方法。我们正常情况可能会这样写:

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为元素添加事件的时候,(eg. IE6/IE7)都会走一遍if...else if ...,其实只要一次判定就可以了,怎么做?–柯里化。改为下面这样子的代码:

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的执行其实值实现了部分的应用(只有一次的if...else if...判定),而剩余的参数应用都是其返回函数实现的,典型的柯里化。

3、延迟计算/运行:不断的柯里化,累积传入的参数,最后执行

var curryWeight = function(fn) {
    var _fishWeight = [];
    return function() {
        if (arguments.length === 0) {
            return fn.apply(null, _fishWeight);
        } else {
            _fishWeight = _fishWeight.concat([].slice.call(arguments));
        }
    }
};
var fishWeight = 0;
var addWeight = curryWeight(function() {
    var i=0; len = arguments.length;
    for (i; i<len; i+=1) {
        fishWeight += arguments[i];
    }
});

addWeight(2.3);
addWeight(6.5);
addWeight(1.2);
addWeight(2.5);
addWeight();    //  这里才计算

console.log(fishWeight);    // 12.5

通用写法:

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;
    }
}

 一道柯里化的题

实现一个add方法,使计算结果能够满足如下预期:

add(1)(2)(3) = 6

add(1, 2, 3)(4) = 10

add(1)(2)(3)(4)(5) = 15

其实这里的需求是我们在柯里化的过程中既能返回一个函数继续接受剩下的参数,又能就此输出当前的一个结果。

这里就需要使用函数的toString来完成。 当我们返回函数的时候,会调用函数的toString来完成隐式转换,这样输出的就不是函数的字符串形式而是我们定义的toString返回的值。这样就既可以保持返回一个函数,又能够得到一个特定的值。

function add(){
    var args = [].slice.call(arguments);
    var fn = function(){
        var newArgs = args.concat([].slice.call(arguments));
        return add.apply(null,newArgs);
    } 
    fn.toString = function(){
        return args.reduce(function(a, b) {
            return a + b;
        })
    }
    return fn ;
}
add(1)(2,3) //6
add(1)(2)(3)(4)(5) //15

这样这个函数可以接受任意个数的参数,被调用任意次。 
调用过程:

  • add(1),返回:
     function(){
            var newArgs = [1].concat([].slice.call(arguments));
            return add.apply(null,newArgs);
    }

  同时返回值为此函数的toString结果1。

  • add(1)(2,3),相当于:
    (function(){
            var newArgs = args.concat([].slice.call(arguments));
            return add.apply(null,newArgs);
    })(2,3);

    此时新参数newArgs为[1,2,3],同时再次调用add,返回函数:

    function(){
            var newArgs = [1,2,3].concat([].slice.call(arguments));
            return add.apply(null,newArgs);
    }

  并且此函数的值为toString的结果即6,因此可以输出6。 
其实就是每次都更新当前的参数,重新调用一下add函数,并计算当前为止的结果。

posted @ 2018-04-10 11:40  L_mj  阅读(489)  评论(0编辑  收藏  举报