1. 程式人生 > >JavaScript函數的柯裏化(currying)

JavaScript函數的柯裏化(currying)

HR rip 求解 type targe font 可能 上下 實例

轉載請註明出處:http://www.cnblogs.com/shamoyuu/p/currying.html

什麽是js函數的currying /柯裏化?

說到js的柯裏化,相信很多朋友都會頭大。或者不是很清楚。我今天簡單的給大家介紹一下。

我用一句話總結函數柯裏化,js柯裏化是逐步傳參,逐步縮小函數的適用範圍,逐步求解的過程

可能對這句話你不是很清楚,那麽,我們來看個案例,簡單說明一下:

需求:我們寫一個函數,將函數的幾個參數相加,返回結果!那我們寫的函數如下

var concat3Words = function (a, b, c) {
    return a+b+c;
};

函數柯裏化呢?是分部求解,先傳一個a參數,再傳一個b參數,再傳一個c參數,最後將這三個參數相加!

var concat3WordsCurrying = function(a) {
    return function (b) {
        return function (c) {
            return a+b+c;
        };
    };
};

我們看輸出結果:

console.log(concat3Words("foo ","bar ","baza"));            // foo bar baza
console.log(concat3WordsCurrying("foo ")); // [Function] console.log(concat3WordsCurrying("foo ")("bar ")("baza")); // foo bar baza 函數鏈式調用

柯裏化案例

上面的需求我們稍微變一下。現在我們更進一步,如果要求可傳遞的參數不止3個,可以傳任意多個參數,當不傳參數時輸出結果?那現在我們用柯裏化來簡單的實現一下:

var adder = function () {
    var _args = [];
    
return function () { if (arguments.length === 0) { return _args.reduce(function (a, b) { return a + b; }); } [].push.apply(_args, [].slice.call(arguments)); return arguments.callee; } }; var sum = adder(); console.log(sum); // Function sum(100,200)(300); // 調用形式靈活,一次調用可輸入一個或者多個參數,並且支持鏈式調用 sum(400); console.log(sum()); // 1000 (加總計算)

上面 adder是柯裏化了的函數,它返回一個新的函數,新的函數接收可分批次接受新的參數,延遲到最後一次計算。我們可以任意傳入參數,當不傳參數的時候,輸出計算結果!

基礎知識普及之arguments

看到上面的柯裏化實現,可能有的朋友會有點暈,其實上面也沒有什麽新的知識,假如你看過我之前寫的文章,相信能夠理解!apply,call之前都有介紹。前端幹貨中的第四條,也有提及!唯獨arguments前面文章沒有介紹。

arguments對象是比較特別的一個對象,實際上是當前函數的一個內置屬性。arguments非常類似Array,但實際上又不是一個Array實例。

我們看一下下面的例子:

function f(a, b, c){
    alert(arguments.length);   // result: "2"
    a = 100;
    alert(arguments[0]);       // result: "100"
    arguments[0] = "haorooms";
    alert(a);                  // result: "haorooms"
    alert(c);                  // result: "undefined"
    c = 2016;
    alert(arguments[2]);       // result: "undefined"
}

f(1, 2);

我們通常用

[].slice.call(arguments, 1)

將傳入參數轉為數組。slice是js的一個函數,關於其用法,不會的去查一下!上面的寫法相當於Array.prototype.slice.call(arguments, 1);

另外,arguments對象中有一個非常有用的屬性:callee。arguments.callee返回此arguments對象所在的當前函數引用。在使用函數遞歸調用時推薦使用arguments.callee代替函數名本身。arguments就介紹到這裏!

通用的柯裏化函數

下面這個是通用的柯裏化函數

var currying = function (fn) {
    var _args = [];
    return function () {
        if (arguments.length === 0) {
            return fn.apply(this, _args);
        }
        Array.prototype.push.apply(_args, [].slice.call(arguments));
        return arguments.callee;
    }
};

我們可以通過如下函數簡單應用一下柯裏化:

var multi=function () {
    var total = 0;
    for (var i = 0, c; c = arguments[i++];) {
        total += c;
    }
    return total;
};

var sum = currying(multi);  

sum(100,200)(300);
sum(400);
console.log(sum());     // 1000  (空白調用時才真正計算)

增加適用性的柯裏化

上面我的介紹的是柯裏化延遲執行,縮小範圍!還有一種是增加了函數的適用性,但同時也降低了函數的適用範圍。其通用寫法如下:

function currying(fn) {
    var slice = Array.prototype.slice,
    __args = slice.call(arguments, 1);
    return function () {
        var __inargs = slice.call(arguments);
        return fn.apply(null, __args.concat(__inargs));
    };
}

例如:

function square(i) {
    return i * i;
}
function map(handeler, list) {
    return list.map(handeler);
}
// 數組的每一項平方
map(square, [1, 2, 3, 4, 5]);
map(square, [6, 7, 8, 9, 10]);
map(square, [10, 20, 30, 40, 50]);

例子中,創建了一個map通用函數,用於適應不同的應用場景。顯然,通用性不用懷疑。同時,例子中重復傳入了相同的處理函數:square。

柯裏化改造:

function square(i) {
    return i * i;
}
function map(handeler, list) {
    return list.map(handeler);
}
var mapSQ = currying(map, square);
mapSQ([1, 2, 3, 4, 5]);
mapSQ([6, 7, 8, 9, 10]);
mapSQ([10, 20, 30, 40, 50]);

Function.prototype.bind 方法也是柯裏化應用

與 call/apply 方法直接執行不同,bind 方法 將第一個參數設置為函數執行的上下文,其他參數依次傳遞給調用方法(函數的主體本身不執行,可以看成是延遲執行),並動態創建返回一個新的函數, 這符合柯裏化特點。

var foo = {x: 888};
var bar = function () {
    console.log(this.x);
}.bind(foo); // 綁定
bar();


完結,散花

JavaScript函數的柯裏化(currying)