JavaScript偏函數(shù)與柯里化實(shí)例詳解
本文實(shí)例講述了JavaScript偏函數(shù)與柯里化。分享給大家供大家參考,具體如下:
到目前為止我們僅討論綁定this,現(xiàn)在讓我們更深入學(xué)習(xí)。
我們不僅能綁定this,也可以是參數(shù),這較少使用,但有時(shí)很方便。
bind完整的語(yǔ)法為:
let bound = func.bind(context, arg1, arg2, ...);
可以綁定上下文this
和函數(shù)的初始參數(shù)。舉例,我們有個(gè)乘法函數(shù)mul(a,b)
:
function mul(a, b) { return a * b; }
我們可以在該函數(shù)的基礎(chǔ)上使用綁定創(chuàng)建一個(gè)double
函數(shù):
let double = mul.bind(null, 2); alert( double(3) ); // = mul(2, 3) = 6 alert( double(4) ); // = mul(2, 4) = 8 alert( double(5) ); // = mul(2, 5) = 10
調(diào)用mul.bind(null, 2)
創(chuàng)建新函數(shù)double
,傳遞調(diào)用mul
函數(shù),固定第一個(gè)參數(shù)上下文為null,第二個(gè)參數(shù)為2,多個(gè)參數(shù)傳遞也是如此。
這稱為偏函數(shù)應(yīng)用——我們創(chuàng)造一個(gè)新函數(shù),讓現(xiàn)有的一些參數(shù)值固定。
注意,這里確實(shí)不用this,但bind需要,所以必須使用null。
在下面代碼中函數(shù)triple
實(shí)現(xiàn)乘以3的功能:
let triple = mul.bind(null, 3); alert( triple(3) ); // = mul(3, 3) = 9 alert( triple(4) ); // = mul(3, 4) = 12 alert( triple(5) ); // = mul(3, 5) = 15
為什么我們通常使用偏函數(shù)?
這里我們偏函數(shù)的好處是:通過(guò)創(chuàng)建一個(gè)名稱易懂的獨(dú)立函數(shù)(double,triple),調(diào)用是無(wú)需每次傳入第一個(gè)參數(shù),因?yàn)榈谝粋€(gè)參數(shù)通過(guò)bind提供了固定值。
另一種使用偏函數(shù)情況是,當(dāng)我們有一個(gè)很通用的函數(shù),為了方便提供一個(gè)較常用的變體。
舉例,我們有一個(gè)函數(shù)send(from, to, text)
,那么使用偏函數(shù)可以創(chuàng)建一個(gè)從當(dāng)前用戶發(fā)送的變體:sendTo(to, text)
使用沒(méi)有上下文的偏函數(shù)
如果想固定一些參數(shù),但不綁定this呢?
內(nèi)置的bind
不允許這樣,我們不能忽略上下文并跳轉(zhuǎn)到參數(shù)。幸運(yùn)的是,可以僅綁定參數(shù)partial
函數(shù)容易實(shí)現(xiàn)。
如下:
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 argument user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes()); user.sayNow("Hello"); // Something like: // [10:00] Hello, John!
調(diào)用partial(func[, arg1, arg2...])
函數(shù)的結(jié)果為調(diào)用func
的包裝器(*號(hào)行):
- this一致(因?yàn)?code>user.sayNow是通過(guò)
user
調(diào)用的) - 然后給其...garsBound—— partial使用該參數(shù)("10:00")進(jìn)行調(diào)用。
- 然后提供參數(shù)...gars——提供給包裝器的參數(shù)(“Hello“)
所以使用spread運(yùn)算符很容易實(shí)現(xiàn),是嗎?
loadash庫(kù)也提供了—.partial實(shí)現(xiàn)。
柯里化
有時(shí)人們混淆上面提及的偏函數(shù)和另一個(gè)名稱為“柯里化”函數(shù)功能,柯里化是另一個(gè)有趣的處理函數(shù)技術(shù),這里我們必須要涉及。
柯里化(Currying):轉(zhuǎn)換一個(gè)調(diào)用函數(shù)f(a,b,c)
為f(a)(b)(c)
方式調(diào)用。
讓我們實(shí)現(xiàn)柯里化函數(shù),執(zhí)行一個(gè)兩元參數(shù)函數(shù),即轉(zhuǎn)換f(a,b)
至f(a)(b)
:
function curry(func) { return function(a) { return function(b) { return func(a, b); }; }; } // usage function sum(a, b) { return a + b; } let carriedSum = curry(sum); alert( carriedSum(1)(2) ); // 3
上面是通過(guò)一系列包裝器實(shí)現(xiàn)的。
curry(func)
的結(jié)果是function(a)
的一個(gè)包裝器。- 當(dāng)調(diào)用
sum(1)
是,參數(shù)被保存在詞法環(huán)境中,然后返回新的包裝器function(b)
- 然后
sum(1)(2)
提供2并最終調(diào)用function(b)
,然后傳遞調(diào)用給原始多參數(shù)函數(shù)sum
。
有一些柯里化的高級(jí)實(shí)現(xiàn),如lodash庫(kù)中_.curry可以實(shí)現(xiàn)更復(fù)雜功能。其返回一個(gè)包裝器,它允許函數(shù)提供全部參數(shù)被正常調(diào)用或返回偏函數(shù)。
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 }; }
柯里化?應(yīng)用場(chǎng)景?
高級(jí)柯里化允許函數(shù)正常調(diào)用,也可以容易以偏函數(shù)方式調(diào)用。為了理解其優(yōu)勢(shì),我們需要一個(gè)實(shí)際的示例說(shuō)明。
舉例,我們有日志函數(shù)log(date,importance,message)
,格式化輸出信息。實(shí)際項(xiàng)目中這些函數(shù)也有許多其他有用的特性,如:通過(guò)網(wǎng)絡(luò)發(fā)送或過(guò)濾:
function log(date, importance, message) { alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`); }
讓我們使用柯里化!
log = _.curry(log);
柯里化后仍然可以正常調(diào)用:log(new Date(), "DEBUG", "some debug")
;
我們也可以使用柯里化方式調(diào)用:log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)
這里定義一個(gè)便捷函數(shù),記錄當(dāng)天日志:
// todayLog will be the partial of log with fixed first argument let todayLog = log(new Date()); // use it todayLog("INFO", "message"); // [HH:mm] INFO message
現(xiàn)在再定義一個(gè)便捷函數(shù):記錄當(dāng)天debug信息:
let todayDebug = todayLog("DEBUG"); todayDebug("message"); // [HH:mm] DEBUG message
所以:
1. 柯里化后沒(méi)有失去任何東西,log仍然可以正常調(diào)用。
2. 我們能生成在多個(gè)場(chǎng)景使用的便捷偏函數(shù)。
高級(jí)柯里化實(shí)現(xiàn)
如果你感興趣,這里提供了上面提到的高級(jí)柯里化實(shí)現(xiàn):
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 normally alert( curriedSum(1, 2, 3) ); // 6 // get the partial with curried(1) and call it with 2 other arguments alert( curriedSum(1)(2,3) ); // 6
這里實(shí)現(xiàn)看上去有點(diǎn)復(fù)雜,但確實(shí)很容易理解。curry(func)的結(jié)果是包裝器curried,如下所示:
// func is the function to transform function 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)); } } };
當(dāng)我們運(yùn)行時(shí),有兩個(gè)分支:
1. 如果傳遞args數(shù)與原函數(shù)已經(jīng)定義的參數(shù)個(gè)數(shù)一樣或更長(zhǎng),那么直接調(diào)用。
2. 獲得偏函數(shù):否則,不調(diào)用func函數(shù),返回另一個(gè)包裝器pass,提供連接之前的參數(shù)一起做為新參數(shù)重新應(yīng)用curried。然后再次執(zhí)行一個(gè)新調(diào)用,返回一個(gè)新偏函數(shù)(如果參數(shù)不夠)或最終結(jié)果。
舉例,讓我們看sum(a, b, c)
會(huì)怎樣,三個(gè)參數(shù),所以sum.length=3
.
如果調(diào)用curried(1)(2)(3)
:
1. 第一次調(diào)用curried(1)
,在詞法環(huán)境中記住1,返回包裝器pass
。
2. 使用(2)調(diào)用包裝器pass
:其帶著前面的參數(shù)(1),連接他們?nèi)缓笳{(diào)用curried(1,2)
,因?yàn)閰?shù)數(shù)量仍然小于3,返回pass。
3. 再次使用(3)被調(diào)用包裝器pass,帶著之前的參數(shù)(1,2),然后增加3,并調(diào)用curried(1,2,3)
——最終有三個(gè)參數(shù),傳遞給原始函數(shù)。
如果仍然不清除,可以按順序在腦子里或紙上跟蹤調(diào)用過(guò)程。
僅針對(duì)函數(shù)參數(shù)長(zhǎng)度固定
柯里化需要函數(shù)有已知的參數(shù)數(shù)量固定。
比柯里化多一點(diǎn)
根據(jù)柯里化定義,轉(zhuǎn)換sum(a,b,c)
至sum(a)(b)(c)
.
但在Javascript中大多數(shù)實(shí)現(xiàn)是超越定義,也可以讓函數(shù)使用多個(gè)參數(shù)變量執(zhí)行。
總結(jié)
當(dāng)把已知函數(shù)的一些參數(shù)固定,結(jié)果函數(shù)被稱為偏函數(shù),通過(guò)使用bind獲得偏函數(shù),也有其他方式實(shí)現(xiàn)。
當(dāng)我們不想一次一次重復(fù)相同的參數(shù)時(shí),偏函數(shù)是很便捷的。如我們有send(from,to)
函數(shù),如果from總是相同的,可以使用偏函數(shù)簡(jiǎn)化調(diào)用。
柯里化是轉(zhuǎn)換函數(shù)調(diào)用從f(a,b,c)
至f(a)(b)(c)
.Javascript通常既實(shí)現(xiàn)正常調(diào)用,也實(shí)現(xiàn)參數(shù)數(shù)量不足時(shí)的偏函數(shù)方式調(diào)用。
當(dāng)我們想容易的偏函數(shù)時(shí),柯里化非常好。如我們已經(jīng)看到的日志示例:通用的函數(shù)是log(date,importance,message)
,柯里化之后獲得偏函數(shù)為,一個(gè)參數(shù)如log(date)
,或兩個(gè)參數(shù)log(date,importance)
.
更多關(guān)于JavaScript相關(guān)內(nèi)容可查看本站專題:《JavaScript常用函數(shù)技巧匯總》、《javascript面向?qū)ο笕腴T(mén)教程》、《JavaScript錯(cuò)誤與調(diào)試技巧總結(jié)》、《JavaScript數(shù)據(jù)結(jié)構(gòu)與算法技巧總結(jié)》及《JavaScript數(shù)學(xué)運(yùn)算用法總結(jié)》
希望本文所述對(duì)大家JavaScript程序設(shè)計(jì)有所幫助。
相關(guān)文章
使用javascript創(chuàng)建快捷方式的簡(jiǎn)單實(shí)例
這篇文章介紹了使用javascript創(chuàng)建快捷方式的簡(jiǎn)單實(shí)例,有需要的朋友可以參考一下2013-08-08window resize和scroll事件的基本優(yōu)化思路
在項(xiàng)目中使用scroll事件去加載數(shù)據(jù),結(jié)果IE下悲劇了。下面為大家介紹下window resize和scroll事件的基本優(yōu)化思路,需要的朋友可以參考下2014-04-04javascript調(diào)試之DOM斷點(diǎn)調(diào)試法使用技巧分享
在開(kāi)發(fā)中,偶爾會(huì)遇到類似這樣的問(wèn)題:頁(yè)面上的一個(gè)DOM元素被改了屬性,但是我們卻不知道是哪個(gè)腳本更改的2014-04-04使用BootStrap實(shí)現(xiàn)用戶登錄界面UI
本文給大家介紹使用BootStrap實(shí)現(xiàn)用戶登錄界面UI,布局風(fēng)格采用左右各一半的風(fēng)格設(shè)計(jì),非常不錯(cuò),具有參考借鑒價(jià)值,感興趣的朋友一起學(xué)習(xí)吧2016-08-08JavaScript實(shí)現(xiàn)數(shù)字?jǐn)?shù)組按照倒序排列的方法
這篇文章主要介紹了JavaScript實(shí)現(xiàn)數(shù)字?jǐn)?shù)組按照倒序排列的方法,涉及javascript中sort方法的使用技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04js獲取鍵盤(pán)按鍵響應(yīng)事件(兼容各瀏覽器)
js獲取鍵盤(pán)按鍵響應(yīng)事件(兼容各瀏覽器),需要的朋友可以參考一下2013-05-05