相關推薦:javascript教學
apply(context,[arguments])
, call(context,param1,param2,...)
。柯里化(Currying)是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受餘下的參數且返回結果的新函數的技術。
這裡舉個例子,有一個add()
函數,它是用來處理我們傳給它的參數(param1,params2,…)相加求和的一個函數。
// 在這裡第一個有兩個參數`x`、`y`的`add(x , y)`函數function add(x , y){ return x + y; } // 呼叫`add()`函數,並給定兩個參數`4`和`6` add(4,6); // 模擬電腦操作,第一步傳入第一個參數4 function add(4 , y){ return 4 + y; } // 模擬電腦操作,第二步傳入第一個參數6 function add(4 , 6){ return 4 + 6; }
如果我們將add()
函式柯里化,是什麼樣子呢?這裡簡單的實作一下:
// 柯里化過的add()函數,可以接受部分參數function add(x ,y){ if (typeof y === 'undefined') { return function (newy){ return x + newy; } } // 完整應用程式 return x + y; } // 測試呼叫console.log(typeof add(4)); // [Function] console.log(add(4)(6)); // 10 // 可以建立保存函式let saveAdd = add(4); console.log(saveAdd(6)); // 10
從以上簡單柯里化的add()
函數可以看出,函數可以接受部分函數,然後傳回一個新的函數,使其繼續處理剩下的函數。
在這裡我們創建一個公共的柯里化函數,這樣我們就不必每次寫一個函數都要在其內部實現複雜的柯里化過程。
// 定義一個createCurry的函數function createCurry(fn){ var slice = Array.prototype.slice, stored_args = slice.call(arguments,1); return function () { let new_args = slice.call(arguments), args = stored_args.concat(new_args); return fn.apply(null,args); }}
在以上公共的柯里化函數中:
arguments
,並不是一個真的數組,只是一個具有length
屬性的對象,所以我們從Array.prototype
中藉用slice
方法幫我們把arguments
轉為一個真正的數組,方便我們更好的操作。createCurry
的時候,其中變數stored_args
是保持了除去第一個參數以外的參數,因為第一個參數是我們需要柯里化的函數。createCurry
函數中傳回的函數時,變數new_args
取得參數並轉為陣列。stored_args
中儲存的值和變數new_args
的值合併為一個新的數組,並賦值給變數args
。fn.apply(null,args)
方法,執行被柯里化的函數。現在讓我們來測試公共的柯里化函數
// 普通函數add() function add(x , y){ return x + y; } // 柯里化得到一個新的函數var newAdd = createCurry(add,4); console.log(newAdd(6)); // 10 //另一種簡單方式console.log(createCurry(add,4)(6));// 10
當然這裡不限於兩個參數的柯里化,也可以多個參數:
// 多個參數的普通函數function add(a,b,c,d){ return a + b + c + d; } // 柯里化函數得到新函數,多個參數可以隨意分割console.log(createCurry(add,4,5)(5,6)); // 20 // 兩步驟柯里化let add_one = createCurry(add,5); console.log(add_one(5,5,5));// 20 let add_two = createCurry(add_one,4,6); console.log(add_two(6)); // 21
透過以上的例子,我們可以發現一個局限,那就是不管是兩個參數還是多個參數,它只能分兩步執行,如以下公式:
如果我們想更靈活一點:
我們該怎麼實現呢?
經過以上練習,我們發現我們創建的柯里化函數存在一定局限性,我們希望函數可以分為多步驟執行:
// 創建一個可以多步執行的柯里化函數,當參數滿足數量時就去執行它: // 函數公式:fn(x,y,z,w) ==> fn(x)(y)(z)(w); let createCurry = (fn,...params)=> { let args = parsms || []; let fnLen = fn.length; // 指定柯里化函數的參數長度 return (...res)=> { // 透過作用域鏈取得上一次的所有參數 let allArgs = args.slice(0); // 深度拷貝閉包共用的args參數,避免後續操作影響(引用類型) allArgs.push(...res); if(allArgs.length < fnLen){ // 當參數數量小於原函數的參數長度時,遞歸呼叫createCurry函數 return createCurry.call(this,fn,...allArgs); }else{ // 當參數數量滿足時,觸發函數執行 return fn.apply(this,allArgs); } } } // 多個參數的普通函數function add(a,b,c,d){ return a + b + c + d; } // 測試柯里化函數let curryAdd = createCurry(add,1); console.log(curryAdd(2)(3)(4)); // 10
以上我們已經實作了靈活的柯里化函數,但這裡我們又發現了一個問題:
curryAdd(add,1,2,3,4)()
;add()
函數就行了,這也是一種辦法;但是我們在這裡既然是滿足參數數量,對於這種情況我們還是處理一下。在這裡我們只需要在回傳函數前做判斷就行了:
let createCurry = (fn,...params)=> { let args = parsms || []; let fnLen = fn.length; // 指定柯里化函數的參數長度 if(length === _args.length){ // 加入判斷,如果第一次參數數量以經足夠時就直接呼叫函數取得結果return fn.apply(this,args); } return (...res)=> { let allArgs = args.slice(0); allArgs.push(...res); if(allArgs.length < fnLen){ return createCurry.call(this,fn,...allArgs); }else{ return fn.apply(this,allArgs); } }}
以上可以算是完成了一個靈活的柯里化的函數了,但是這裡還不算很靈活,因為我們不能控制它什麼時候執行,只要參數數量足夠它就自動執行。我們希望實現一個可以控制它執行的時機該怎麼辦?
我們在這裡直接說明一下函數公式:
// 當參數滿足,再次執行時呼叫函數let createCurry = (fn,...params)=> { let args = parsms || []; let fnLen = fn.length; // 指定柯里化函數的參數長度 //當然這裡的判斷需要註解掉,不然當它第一次參數數量足夠時就直接執行結果了 //if(length === _args.length){ // 加入判斷,如果第一次參數數量以經足夠時就直接呼叫函數取得結果//return fn.apply(this,args); //} return (...res)=> { let allArgs = args.slice(0); allArgs.push(...res); // 在這裡判斷輸入的參數是否大於0,如果大於0在判斷參數數量是否足夠, // 這裡不能用&& ,如果用&& 也是參數數量足夠時就執行結果了。 if(res.length > 0 || allArgs.length < fnLen){ return createCurry.call(this,fn,...allArgs); }else{ return fn.apply(this,allArgs); } } } // 多個參數的普通函數function add(a,b,c,d){ return a + b + c + d; } // 測試可控制的柯里化函數let curryAdd = createCurry(add,1); console.log(curryAdd(2)(3)(4)); // function console.log(curryAdd(2)(3)(4)()); // 10 console.log(curryAdd(2)(3)()); // 當參數不足夠時回傳NaN
相關推薦:javascript學習教學
以上就是一起來聊聊JavaScript函數柯里化的詳細內容,更多請關注php中文網其它相關文章!