javascript
深入理解javascript系列(十七):函数柯里化
之前的系列,我們介紹了什么是高階函數(shù)。所有以函數(shù)作為參數(shù)的函數(shù),都可以叫作高階函數(shù)。并且我們常常利用高階函數(shù)來封裝一些公共邏輯。
本次,我們要繼續(xù)學(xué)習(xí),繼續(xù)記錄,柯里化。柯里化,其實(shí)就是高階函數(shù)的一種特殊用法。
柯里化是指這樣一個(gè)函數(shù)(假設(shè)叫做createCurry),它接收函數(shù)A作為參數(shù),運(yùn)行后能夠返回一個(gè)新的函數(shù),并且這個(gè)新的函數(shù)能夠處理函數(shù)A的剩余參數(shù)。
文字總是不那么好去理解,下面我們就通過例子來理解吧。
假設(shè)有一個(gè)接收三個(gè)參數(shù)的函數(shù)A。
function A(a, b, c) {// to do something }復(fù)制代碼又假設(shè)我們有一個(gè)已經(jīng)封裝好了的柯里化通用函數(shù)createCurry。他接收bar作為參數(shù),能夠?qū)轉(zhuǎn)化為柯里化函數(shù),返回結(jié)果就是這個(gè)被轉(zhuǎn)化之后的函數(shù)。
var _A = createCurry(A);復(fù)制代碼那么_A作為createCurry運(yùn)行的返回函數(shù),能夠處理A的剩余參數(shù)。因此下面的運(yùn)行結(jié)果都是等價(jià)的。
_A(1, 2, 3); _A(1,2)(3); _A(1)(2,3); _A(1)(2)(3); A(1,2,3);復(fù)制代碼函數(shù)A被createCurry轉(zhuǎn)化之后得到柯里化函數(shù)_A,_A能夠處理A的所有剩余參數(shù)。因此柯里化也被稱為部分求值。
在簡(jiǎn)單的場(chǎng)景下,我們可以不借助柯里化通用式來轉(zhuǎn)化得到柯里化函數(shù),僅憑借眼力自己封裝。
例如,有一個(gè)簡(jiǎn)單的加法函數(shù),它能夠?qū)⒆陨淼娜齻€(gè)參數(shù)加起來并返回計(jì)算結(jié)果。
function add(a, b, c) {return a + b + c; }復(fù)制代碼那么add函數(shù)的柯里化函數(shù)_add則可以寫成:
function _add(a) {return function(b) {return function(c) {return a + b + c;}} }復(fù)制代碼因此下面的運(yùn)算方式是等價(jià)的。
add(1, 2, 3); _add(1)(2)(3);復(fù)制代碼當(dāng)然,柯里化通用式具備更加強(qiáng)大的能力,僅靠眼力勁可不行。因此我們更需要知道如何封裝這樣一個(gè)柯里化的通用式。
首先通過_add可以看出,柯里化函數(shù)的運(yùn)行過程其實(shí)是一個(gè)參數(shù)收集過程,我們將每一次傳入的參數(shù)收集起來,并在最里層進(jìn)行處理。因此在實(shí)現(xiàn)createCurry時(shí),可以借助這個(gè)思路來進(jìn)行封裝。
代碼如下:
// arity 用來標(biāo)記剩余參數(shù)的個(gè)數(shù) // args 用來收集參數(shù)function createCurry(func, arity, args) {//第一次執(zhí)行時(shí),并不會(huì)傳入arity,而是直接獲取func參數(shù)的個(gè)數(shù) func.lengthvar arity = arity || func.length;//第一次執(zhí)行也不會(huì)傳入args,而是默認(rèn)為空數(shù)組var args = args || [];var wrapper = function() {//將wrapper中的參數(shù)收集到args中var _args = [].slice.call(arguments);[].push.apply(args, _args);//如果參數(shù)個(gè)數(shù)小于最初的func.length,則遞歸調(diào)用,繼續(xù)收集參數(shù)if(_args.length < arity) {arity -= _args.length;return createCurry(func, arity, args);}//參數(shù)收集完畢,執(zhí)行funcreturn func.apply(func, args);}return wrapper; }復(fù)制代碼是不是有些不太容易理解,所以要多閱讀幾次。這個(gè)createCurry的封裝其實(shí)是借助了閉包和遞歸,實(shí)現(xiàn)一個(gè)參數(shù)收集,并在收集完畢之后執(zhí)行所有參數(shù)。
不知道您是否有發(fā)現(xiàn),函數(shù)經(jīng)過createCurry轉(zhuǎn)化為一個(gè)柯里化函數(shù)后,最后執(zhí)行的結(jié)果,不是正相當(dāng)于執(zhí)行函數(shù)自己?jiǎn)?#xff1f;柯里化是不是把簡(jiǎn)單的問題復(fù)雜化了?
沒錯(cuò),柯里化確實(shí)是把簡(jiǎn)單的問題復(fù)雜化了,但在復(fù)雜化的同時(shí),我們?cè)谑褂煤瘮?shù)時(shí)擁有了更多的自由度。對(duì)于函數(shù)參數(shù)的自由處理,正是柯里化的核心所在。
下面舉一個(gè)常見的例子。
如果想要驗(yàn)證一串?dāng)?shù)字是否是正確的手機(jī)號(hào),那么按照正常思路來做,可能就會(huì)寫出代碼如下唉:
fuction checkPhone(phoneNumber) {return /^1[34578]\d{9}$/.test(phoneNumber); }復(fù)制代碼而如果想要驗(yàn)證是否是郵箱呢?你然后在寫一個(gè),可是我們還會(huì)遇到更多需要驗(yàn)證的消息,如“身份證、登錄名、密碼...”。為了偷懶,我們應(yīng)該封裝一個(gè)更為通用的函數(shù),把待驗(yàn)證的正則表達(dá)式與將要被驗(yàn)證的字符串作為參數(shù)傳入:
function check(reg, targetString) {return reg.test(targetSting); }復(fù)制代碼但是這樣封裝之后,在使用時(shí)又會(huì)遇到問題,因?yàn)榭偸切枰斎胍淮齽t,一串字符,這樣就導(dǎo)致使用時(shí)效率低下。
這個(gè)時(shí)候,我們就可以借助柯里化,在check的基礎(chǔ)上再做一層封裝,以簡(jiǎn)化使用。
var _check = createCurry(check);var checkPhone = _check(/xxxxxx/); var checkEmail = _check(/xxxxxx/);復(fù)制代碼最后在使用時(shí)就會(huì)變得更加簡(jiǎn)潔與直觀了。
checkPhone('13979227922'); checkEmail('xsxsx@163.com');復(fù)制代碼在這個(gè)過程中可以發(fā)現(xiàn),柯里化能夠應(yīng)對(duì)更加復(fù)雜的邏輯封裝。當(dāng)情況變得多變時(shí),柯里化依然能夠應(yīng)付自如。
雖然柯里化在一定程度上將問題復(fù)雜化,也讓代碼變得更加不容易理解,但是柯里化在面對(duì)復(fù)雜情況時(shí)的靈活性卻讓我們不得不愛。
總結(jié)
以上是生活随笔為你收集整理的深入理解javascript系列(十七):函数柯里化的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 编程开发之--Oracle数据库--存储
- 下一篇: 详解JavaScript之神奇的Obje