JavaScript函數的柯裏化(currying)
轉載請註明出處: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 bazaconsole.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)