1. 程式人生 > >js中的柯裏化

js中的柯裏化

span 入參 -c cap 瀏覽器 科學 list 處理 兼容

維基百科中的解釋:

  在計算機科學中,柯裏化(英語: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函數,並計算當前為止的結果。

js中的柯裏化