a(b(c(x)));這是“包菜式”多層函數呼叫,但不是很優雅。為了解決函數多層呼叫的巢狀問題,我們需要用到函數合成。其語法格式如下:
var f = compose(a, b, c); //合成函數 f(x);例如:
var compose = function (f, g) { return function (x) { return f(g(x)); }; }; var add = function (x) { return x + 1; } //加法運算 var mul = function (x) { return x * 5; } //乘法運算 compose(mul, add) (2); //合併加法運算和乘法運算,返回15在上面程式碼中,compose 函數的作用就是組合函數,將函數串聯起來執行,一個函數的輸出結果是另一個函數的輸入引數,一旦第 1 個函數開始執行,就會像多米諾骨牌一樣推導執行了。
//函數合成,從右到左合成函數 var compose = function () { var _arguments = arguments; //快取外層函數 var length = _arguments.length; //快取長度 var index = length; //定義游標變數 //檢測函數,如果存在非函數引數,則丟擲異常 while (index --) { if (typeof _arguments[index] !== 'function') { throw new TypeError('引數必須為函數!'); } } return function () { var index = length - 1; //定位到最後一個引數下標 //如果存在兩個及以上引數,則呼叫最後一個引數函數,並傳入內層函數;否則直接返回第 1 個引數函數。 var result = length ? _arguments[index].apply(this, arguments) : arguments[0]; //疊代引數函數 while (index -- ) { //把右側函數的執行結果作為引數傳給左側引數函數,並呼叫。 result = _arguments[index].call(this, result); } return result; //返回最左側引數函數的執行結果 } } //反向函數合成,即從左到右合成函數 var composeLeft = function () { return compose.apply(null, [].reverse.call(arguments)); }在上面實現程式碼中,compose 實現是從右到左進行合成,也提供了從左到右的合成,即 composeLeft,同時在 compose 體內新增了一層函數的校驗,允許傳遞一個或多個引數。
var add = function (x) { return x + 5; } //加法允許 var mul= function (x) { return x * 5; } //乘法運算 var sub= function (x) { return x - 5; } //減法運算 var div = function (x) { return x / 5; } //除法運算 var fn = compose(add, mul, sub, div); console.log(fn(50)); //返回30 var fn = compose(add, compose(mul, sub, div)); console.log(fn(50)); //返回30 var fn = compose(compose(add, mul), sub, div); console.log(fn(50)); //返回30上面幾種組合方式都可以,最後都返回 30。注意,排列順序要保持一致。
var add = function (x,y) { return x + y; }每次調動 add(),需要同時傳入兩個引數。如果希望每次僅傳入一個引數,可以這樣進行柯里化。
var add = function (x) { //柯里化 return function (y) { return x + y; } } console.log(add(2) (6)); //8,連續呼叫 var add1 = add(200); console.log(add1(2)); //202,分步呼叫函數 add 接收一個引數,並返回一個函數,這個返回的函數可以再接收一個引數,並返回兩個引數之和。從某種意義上講,這是一種對引數的“快取”,是一種非常高效的函數式運算方法。柯里化在 DOM 的回撥中非常有用。
var add = function (x,y) { return x + y; }柯里化函數:
var curryAdd = curry(add);這個 add 需要兩個引數,但是執行 curryAdd 時,可以傳入更少的引數。當傳入的引數少於 add 需要的引數時,add 函數並不會執行,curryAdd 就會將這個引數記錄下來,並且返回另外一個函數,這個函數可以繼續執行傳入引數。如果傳入引數的總數等於 add 所需引數的總數,則執行原始引數,返回想要的結果。如果沒有引數限制,最後根據空的小括號作為執行原始引數的條件,返回運算結果。
//柯里化函數 function curry (fn) { var _argLen = fn.length; //記錄原始函數的形參個數 var _args = [].slice.call(arguments, 1); //把傳入的第2個及以後引數轉換為陣列 function wrap () { //curry函數 //把當前引數轉換為陣列,與前面引數進行合併 _args = _args.concat([].slice.call(arguments)); function act () { //引數處理常式 //把當前引數轉換為陣列,與前面引數進行合併 _args = _args.concat([].slice.call(arguments)); //如果傳入引數總和大於等於原始引數的個數,觸發執行條件 if ((_argLen == 0 && arguments.length == 0) || (_argLen > 0 && _args.length >= _argLen)) { //執行原始函數,並把每次傳入引數傳入進去,返回執行結果,停止curry return fn.apply(null, _args); } return arguments.callee; } //如果傳入引數大於等於原始函數的引數個數,即觸發了執行條件 if ((_argLen == 0 && arguments.length == 0) || (_argLen > 0 && _args.length >= _argLen)) { //執行原始函數,並把每次傳入引數傳入進去,返回執行結果,停止curry return fn.apply(null, _args); } act.toString = function () { //定義處理常式的字串表示為原始函數的字串表示 return fn.toString(); } return act; //返回處理常式 } return wrap; //返回curry函數 }
//求和函數,引數不限 var add = function () { //疊代所有引數值,返回最後彙總的值 return [].slice.call(arguments).reduce(function (a,b) { //如果元素的值為數值,則參與求和運算,否則設定為0,跳過非數位的值 return (typeof a == "number" ? a : 0) + (typeof b =="number" ? b : 0); }) } //柯里化函數 var curried = curry(add); console.log(curried(1) (2) (3) ()); //6 var curried = curry(add); console.log(curried(1,2,3) (4) ()); //10 var curried = curry(add, 1); console.log(curried(1,2) (3) (3) ()); //10 var curried = curry(add, 1, 5); console.log(curried(1,2,3,4) (5) ()); //21
var add = function (a,b,c) { //求和函數,3個引數之和 return a + b + c; } //柯里化函數 var curried = curry(add, 2); console.log(curried(1) (2)); //5 var curried = curry(add, 2, 1); console.log(curried(2)); //5 var curried = curry(add); console.log(curried (1) (2) (6)); //9 var curried = curry(add); console.log(curried(1,2,6)); //9
curry 函數的設計不是固定的,可以根據具體應用場景靈活客製化。curry 主要有 3 個作用:快取函數、暫緩函數執行、分解執行任務。