您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關JavaScript函數柯里化該怎么理解,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
call 和 apply 都是為了改變某個函數運行時的 context 即上下文而存在的,換句話說,就是為了改變函數體內部 this 的指向。
call 和 apply二者的作用完全一樣,只是接受參數的方式不太一樣。call其實是apply的一種語法糖。
格式: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) ==> fn(x)(y);
fn(x,y,z,w) ==> fn(x)(y,z,w) || fn(x,y)(z,w)||…
如果我們想更靈活一點:
fn(x,y) ==> fn(x)(y);
fn(x,y,z) ==> fn(x,y)(z) || fn(x)(y)(z);
fn(x,y,z,w) ==> fn(x,y)(z)(w) || fn(x)(y)(z)(w) || …;
我們該怎么實現呢?
經過以上練習,我們發現我們創建的柯里化函數存在一定局限性,我們希望函數可以分為多步執行:
// 創建一個可以多步執行的柯里化函數,當參數滿足數量時就去執行它: // 函數公式: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
以上我們已經實現了靈活的柯里化函數,但是這里我們又發現了一個問題:
如果我第一次就把參數全部傳入,但是它并沒有返回結果,而是一個函數(function)。
只有我們再次將返回的函數調用一次才能返回結果: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); } } }
以上可以算是完成了一個靈活的柯里化的函數了,但是這里還不算很靈活,因為我們不能控制它什么時候執行,只要參數數量足夠它就自動執行。我們希望實現一個可以控制它執行的時機該怎么辦呢?
我們這里直接說明一下函數公式:
fn(a,b,c) ==> fn(a)(b)(c )();
fn(a,b,c) ==> fn(a);fn(b);fn(c );fn();
當我們參數足夠時它并不會執行,只有我們再次調用一次函數它才會執行并返回結果。在這里我們在以上例子中加一個小小的條件就可以實現。
// 當參數滿足,再次執行時調用函數 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函數柯里化該怎么理解,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。