引子javascript
有這樣一道題目,實現一個函數,實現以下功能:html
var result = sum(1)(2)(3); console.log(result);//6
這道題目,印象中是一道技術筆試題。結合查到的資料,在這裏作一下簡單的分析和總結。前端
一個簡單的例子java
題目給的仍是比較寬的,沒多少限制,給了不少自由發揮的空間。數組
下面咱們就一步一步的去實現,一種簡單的作法能夠是這樣的:瀏覽器
function add(a){ var sum = 0; sum += a; return function(b){ sum += b; return function(c){ sum += c; return sum; } } } add(1)(2)(3);//6
嗯,沒什麼問題。閉包
在此基礎上,咱們再進一步:app
若是對調用的次數不加限制,好比 四次,那上面的代碼就不行了。 那該怎麼辦呢?
觀察一下,咱們能夠發現返回的每個函數執行的邏輯其實都是同樣的。
就此咱們能夠精簡下代碼,讓函數返回後返回自身。函數
來試一下:性能
function add(a){ var sum = 0; sum += a; return function temp(b) { sum += b; return temp; } } add(2)(3)(4)(5); 輸出的結果: //function temp(b) { // sum += b; // return temp; // }
並無像咱們預期的那樣輸出 14,實際上是這樣的,每次函數調用後返回的就是一個函數對象,那最後的結果,確定是一個字符串表示啊。
要修正的話,有兩個辦法。
function add(a){ var sum = 0; sum += a; return function temp(b) { if (arguments.length === 0) { return sum; } else { sum= sum+ b; return temp; } } } add(2)(3)(4)(5)(); //14
若是要使用匿名函數,也能夠:
function add() { 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 = add(); sum(2,3)(4)(5)(); //14
2 . 利用JS中對象到原始值的轉換規則。
當一個對象轉換成原始值時,先查看對象是否有valueOf方法。
若是有而且返回值是一個原始值,那麼直接返回這個值。
若是沒有valueOf 或 返回的不是原始值,那麼調用toString方法,返回字符串表示。
那咱們就爲函數對象添加一個valueOf方法 和 toString方法:
function add(a) { var sum = 0; sum += a; var temp = function(b) { if(arguments.length===0){ return sum; } else { sum = sum+ b; return temp; } } temp.toString = temp.valueOf = function() { return sum; } return temp; } add(2)(3)(4)(5); //14
寫到這裏,咱們來簡單總結下。
柯里化的定義
柯里化一般也稱部分求值,其含義是給函數分步傳遞參數,每次傳遞參數後,部分應用參數,並返回一個更具體的函數接受剩下的參數,中間可嵌套多層這樣的接受部分參數函數,逐步縮小函數的適用範圍,逐步求解,直至返回最後結果。
一個通用的柯里化函數
var curring = function(fn){ var _args = []; return function cb(){ if(arguments.length === 0) { return fn.apply(this, _args); } Array.prototype.push.apply(_args, [].slice.call(arguments)); return cb; } } var multi = function(){ var total = 0; var argsArray = Array.prototype.slice.call(arguments); argsArray.forEach(function(item){ total += item; }) return total }; var calc = curring(multi); calc(1,2)(3)(4,5,6); console.log(calc()); //空白調用時才真正計算
這樣 calc = currying(multi),調用很是清晰.
若是要 累加多個值,能夠把多個值做爲作個參數 calc(1,2,3),也能夠支持鏈式的調用,如 calc(1)(2)(3);
到這裏, 不難看出,柯里化函數具備如下特色:
說了這麼多,柯里化函數到底可以幫我作什麼,或者說,我爲何要用柯里化函數呢? 咱們接着往下看。
柯里化函數的做用
函數柯里化容許和鼓勵你分隔複雜功能變成更小更容易分析的部分。這些小的邏輯單元顯然是更容易理解和測試的,而後你的應用就會變成乾淨而整潔的組合,由一些小單元組成的組合。
1.提升通用性
function square(i) { return i * i; } function double(i) { return i *= 2; } 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(double, [1, 2, 3, 4, 5]); map(double, [6, 7, 8, 9, 10]); map(double, [10, 20, 30, 40, 50]);
例子中,建立了一個map通用函數,用於適應不一樣的應用場景。顯然,通用性不用懷疑。同時,例子中重複傳入了相同的處理函數:square和dubble。
應用中這種可能會更多。固然,通用性的加強必然帶來適用性的減弱。可是,咱們依然能夠在中間找到一種平衡。
看下面,咱們利用柯里化改造一下:
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 double(i) { return i *= 2; } function map(handeler, list) { return list.map(handeler); } var mapSQ = currying(map, square); mapSQ([1, 2, 3, 4, 5]); //[1, 4, 9, 16, 25] var mapDB = currying(map, double); mapDB([1, 2, 3, 4, 5]); //[2, 4, 6, 8, 10]
咱們縮小了函數的適用範圍,但同時提升函數的適性.
2 延遲執行。
柯里化的另外一個應用場景是延遲執行。不斷的柯里化,累積傳入的參數,最後執行。
3.固定易變因素。
柯里化特性決定了它這應用場景。提早把易變因素,傳參固定下來,生成一個更明確的應用函數。最典型的表明應用,是bind函數用以固定this這個易變對象。
Function.prototype.bind = function(ctx) { var fn = this; return function() { fn.apply(ctx, arguments); }; };
Function.prototype.bind 方法也是柯里化應用與 call/apply 方法直接執行不一樣,bind 方法 將第一個參數設置爲函數執行的上下文,其餘參數依次傳遞給調用方法(函數的主體自己不執行,能夠當作是延遲執行),並動態建立返回一個新的函數, 這符合柯里化特色。
var foo = { x: 666 }; var bar = function () { console.log(this.x); }.bind(foo); // 綁定 bar(); //666 // 下面是一個 bind 函數的模擬,testBind 建立並返回新的函數,在新的函數中將真正要執行業務的函數綁定到實參傳入的上下文,延遲執行了。 Function.prototype.testBind = function (scope) { var self = this; // this 指向的是調用 testBind 方法的一個函數, return function () { return self.apply(scope); } }; var testBindBar = bar.testBind(foo); // 綁定 foo,延遲執行 console.log(testBindBar); // Function (可見,bind以後返回的是一個延遲執行的新函數) testBindBar(); // 666
關於curry性能的備註
一般,使用柯里化會有一些開銷。取決於你正在作的是什麼,可能會或不會,以明顯的方式影響你。也就是說,幾乎大多數狀況,你的代碼的擁有性能瓶頸首先來自其餘緣由,而不是這個。
有關性能,這裏有一些事情必須牢記於心:
以上 ;)
以爲內容有幫助能夠關注下個人公衆號 「 前端e進階 」,掌握最新動態,一塊兒學習成長!
參考資料
http://blog.jobbole.com/77956/
http://www.cnblogs.com/pigtai...