本文實例講述了JavaScript偏函數與柯里化。分享給大家供大家參考,具體如下:
到目前為止我們僅討論綁定this,現在讓我們更深入學習。
我們不僅能綁定this,也可以是參數,這較少使用,但有時很方便。
bind完整的語法為:
let bound = func.bind(context, arg1, arg2, ...);
可以綁定上下文this
和函數的初始參數。舉例,我們有個乘法函數mul(a,b)
:
function mul(a, b) { return a * b;}
我們可以在該函數的基礎上使用綁定創建一個double
函數:
let double = mul.bind(null, 2);alert( double(3) ); // = mul(2, 3) = 6alert( double(4) ); // = mul(2, 4) = 8alert( double(5) ); // = mul(2, 5) = 10
調用mul.bind(null, 2)
創建新函數double
,傳遞調用mul
函數,固定第一個參數上下文為null,第二個參數為2,多個參數傳遞也是如此。
這稱為偏函數應用——我們創造一個新函數,讓現有的一些參數值固定。
注意,這里確實不用this,但bind需要,所以必須使用null。
在下面代碼中函數triple
實現乘以3的功能:
let triple = mul.bind(null, 3);alert( triple(3) ); // = mul(3, 3) = 9alert( triple(4) ); // = mul(3, 4) = 12alert( triple(5) ); // = mul(3, 5) = 15
為什么我們通常使用偏函數?
這里我們偏函數的好處是:通過創建一個名稱易懂的獨立函數(double,triple),調用是無需每次傳入第一個參數,因為第一個參數通過bind提供了固定值。
另一種使用偏函數情況是,當我們有一個很通用的函數,為了方便提供一個較常用的變體。
舉例,我們有一個函數send(from, to, text)
,那么使用偏函數可以創建一個從當前用戶發送的變體:sendTo(to, text)
使用沒有上下文的偏函數
如果想固定一些參數,但不綁定this呢?
內置的bind
不允許這樣,我們不能忽略上下文并跳轉到參數。幸運的是,可以僅綁定參數partial
函數容易實現。
如下:
function partial(func, ...argsBound) { return function(...args) { // (*) return func.call(this, ...argsBound, ...args); }}// Usage:let user = { firstName: "John", say(time, phrase) { alert(`[${time}] ${this.firstName}: ${phrase}!`); }};// add a partial method that says something now by fixing the first argumentuser.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes());user.sayNow("Hello");// Something like:// [10:00] Hello, John!
調用partial(func[, arg1, arg2...])
函數的結果為調用func
的包裝器(*號行):
user.sayNow
是通過user
調用的)所以使用spread運算符很容易實現,是嗎?
loadash庫也提供了—.partial實現。
柯里化
有時人們混淆上面提及的偏函數和另一個名稱為“柯里化”函數功能,柯里化是另一個有趣的處理函數技術,這里我們必須要涉及。
柯里化(Currying):轉換一個調用函數f(a,b,c)
為f(a)(b)(c)
方式調用。
讓我們實現柯里化函數,執行一個兩元參數函數,即轉換f(a,b)
至f(a)(b)
:
function curry(func) { return function(a) { return function(b) { return func(a, b); }; };}// usagefunction sum(a, b) { return a + b;}let carriedSum = curry(sum);alert( carriedSum(1)(2) ); // 3
上面是通過一系列包裝器實現的。
curry(func)
的結果是function(a)
的一個包裝器。sum(1)
是,參數被保存在詞法環境中,然后返回新的包裝器function(b)
sum(1)(2)
提供2并最終調用function(b)
,然后傳遞調用給原始多參數函數sum
。有一些柯里化的高級實現,如lodash庫中_.curry可以實現更復雜功能。其返回一個包裝器,它允許函數提供全部參數被正常調用或返回偏函數。
function curry(f) { return function(..args) { // if args.length == f.length (as many arguments as f has), // then pass the call to f // otherwise return a partial function that fixes args as first arguments };}
柯里化?應用場景?
高級柯里化允許函數正常調用,也可以容易以偏函數方式調用。為了理解其優勢,我們需要一個實際的示例說明。
舉例,我們有日志函數log(date,importance,message)
,格式化輸出信息。實際項目中這些函數也有許多其他有用的特性,如:通過網絡發送或過濾:
function log(date, importance, message) { alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);}
讓我們使用柯里化!
log = _.curry(log);
柯里化后仍然可以正常調用:log(new Date(), "DEBUG", "some debug")
;
我們也可以使用柯里化方式調用:log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)
這里定義一個便捷函數,記錄當天日志:
// todayLog will be the partial of log with fixed first argumentlet todayLog = log(new Date());// use ittodayLog("INFO", "message"); // [HH:mm] INFO message
現在再定義一個便捷函數:記錄當天debug信息:
let todayDebug = todayLog("DEBUG");todayDebug("message"); // [HH:mm] DEBUG message
所以:
1. 柯里化后沒有失去任何東西,log仍然可以正常調用。
2. 我們能生成在多個場景使用的便捷偏函數。
高級柯里化實現
如果你感興趣,這里提供了上面提到的高級柯里化實現:
function curry(func) { return function curried(...args) { if (args.length >= func.length) { return func.apply(this, args); } else { return function(...args2) { return curried.apply(this, args.concat(args2)); } } };}function sum(a, b, c) { return a + b + c;}let curriedSum = curry(sum);// still callable normallyalert( curriedSum(1, 2, 3) ); // 6// get the partial with curried(1) and call it with 2 other argumentsalert( curriedSum(1)(2,3) ); // 6
這里實現看上去有點復雜,但確實很容易理解。curry(func)的結果是包裝器curried,如下所示:
// func is the function to transformfunction curried(...args) { if (args.length >= func.length) { // (1) return func.apply(this, args); } else { return function pass(...args2) { // (2) return curried.apply(this, args.concat(args2)); } }};
當我們運行時,有兩個分支:
1. 如果傳遞args數與原函數已經定義的參數個數一樣或更長,那么直接調用。
2. 獲得偏函數:否則,不調用func函數,返回另一個包裝器pass,提供連接之前的參數一起做為新參數重新應用curried。然后再次執行一個新調用,返回一個新偏函數(如果參數不夠)或最終結果。
舉例,讓我們看sum(a, b, c)
會怎樣,三個參數,所以sum.length=3
.
如果調用curried(1)(2)(3)
:
1. 第一次調用curried(1)
,在詞法環境中記住1,返回包裝器pass
。
2. 使用(2)調用包裝器pass
:其帶著前面的參數(1),連接他們然后調用curried(1,2)
,因為參數數量仍然小于3,返回pass。
3. 再次使用(3)被調用包裝器pass,帶著之前的參數(1,2),然后增加3,并調用curried(1,2,3)
——最終有三個參數,傳遞給原始函數。
如果仍然不清除,可以按順序在腦子里或紙上跟蹤調用過程。
僅針對函數參數長度固定
柯里化需要函數有已知的參數數量固定。
比柯里化多一點
根據柯里化定義,轉換sum(a,b,c)
至sum(a)(b)(c)
.
但在Javascript中大多數實現是超越定義,也可以讓函數使用多個參數變量執行。
總結
當把已知函數的一些參數固定,結果函數被稱為偏函數,通過使用bind獲得偏函數,也有其他方式實現。
當我們不想一次一次重復相同的參數時,偏函數是很便捷的。如我們有send(from,to)
函數,如果from總是相同的,可以使用偏函數簡化調用。
柯里化是轉換函數調用從f(a,b,c)
至f(a)(b)(c)
.Javascript通常既實現正常調用,也實現參數數量不足時的偏函數方式調用。
當我們想容易的偏函數時,柯里化非常好。如我們已經看到的日志示例:通用的函數是log(date,importance,message)
,柯里化之后獲得偏函數為,一個參數如log(date)
,或兩個參數log(date,importance)
.
希望本文所述對大家JavaScript程序設計有所幫助。
新聞熱點
疑難解答