你必須知道的JS柯里化

前言

我們?cè)诟鞣N算法題以及技術(shù)文檔中經(jīng)常會(huì)看到柯里化這個(gè)詞懦尝,那么知纷,柯里化到底是什么?它在js中如何運(yùn)用陵霉?對(duì)我們的編程有什么作用琅轧?都9102年了,如果你還不知道這些踊挠,那么你在面試過(guò)程中很可能會(huì)被面試官diss??


什么是柯里化(Currying)

維基百科解釋是:把接收多個(gè)參數(shù)的函數(shù)變換成接收一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù)乍桂,并返回接受剩余的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。其由數(shù)學(xué)家Haskell Brooks Curry提出效床,并以curry命名睹酌。

簡(jiǎn)單的說(shuō),柯里化函數(shù)持續(xù)地返回一個(gè)新函數(shù)直到所有的參數(shù)用盡為止剩檀。這些參數(shù)全部保持“活著”的狀態(tài)(通過(guò)閉包)忍疾,然后當(dāng)柯里化鏈中的最后一個(gè)函數(shù)被返回和執(zhí)行時(shí)會(huì)全部被用來(lái)執(zhí)行。

這和高階組件(Higher-order functions)如出一轍谨朝。前者返回一個(gè)新函數(shù)卤妒,后者返回一個(gè)新組件甥绿。

舉個(gè)簡(jiǎn)單的栗子

本例使用到的部分ES6知識(shí):constarrow function则披。不了解的同學(xué)可先行查看共缕。
寫(xiě)一個(gè)計(jì)算三個(gè)參數(shù)相乘的函數(shù):

function multiply (a, b, c) {
  return a * b * c
}
multiply(1, 2, 3) // 6

這是我們第一反應(yīng)寫(xiě)出來(lái)的demo,也是看起來(lái)最簡(jiǎn)單的實(shí)現(xiàn)方法士复。再來(lái)創(chuàng)建一個(gè)柯里化版本的函數(shù):

function multiply (a) {
  return (b) => {
    return (c) => {
      return a * b * c
    }
  }
}
multiply(1)(2)(3) // 6

這里我們將multiply(1图谷,2,3)調(diào)用變成了multiply (1) (2) (3) 調(diào)用阱洪。
單獨(dú)一個(gè)函數(shù)被轉(zhuǎn)換成了一系列函數(shù)便贵。為了得到數(shù)字1、2冗荸、3相乘的結(jié)果承璃,這些數(shù)字被一個(gè)接一個(gè)地傳遞,每個(gè)數(shù)字預(yù)填了下一個(gè)函數(shù)內(nèi)聯(lián)調(diào)用蚌本。

我們把multiply (1) (2) (3) 分割一下來(lái)幫助理解:

const mul1 = multiply(1)
const mul2 = mul1(2)
const result = mul2(3)
console.log(result) // 6

當(dāng)mul2使用3作為參數(shù)調(diào)用時(shí)盔粹,它一起使用了之前已拿到的參數(shù)a=1和b=2進(jìn)行運(yùn)算并返回結(jié)果6。

作為一個(gè)嵌套函數(shù)程癌,mul2能夠訪問(wèn)到外部的兩個(gè)函數(shù)multiply和mul1的作用域舷嗡。這就是為什么mul2能利用定義在已經(jīng)‘離場(chǎng)’的函數(shù)中的參數(shù)來(lái)進(jìn)行乘法操作的原因。即使這些函數(shù)早已返回并且從內(nèi)存中垃圾回收了嵌莉,但其變量仍然保持‘活著’(閉包)进萄。你可以看到3個(gè)數(shù)字每次只有1個(gè)提供給函數(shù),并且同一時(shí)間里一個(gè)新函數(shù)會(huì)被返回锐峭,直到所有的數(shù)字用盡為止垮斯。

柯里化背后的邏輯就是獲取一個(gè)函數(shù)并派生出一個(gè)返回特殊函數(shù)的函數(shù),它實(shí)際上是一種思想只祠,或者說(shuō)是一種程序設(shè)計(jì)模式兜蠕。

柯里化的應(yīng)用

1. 編寫(xiě)可以輕松復(fù)用和配置的小代碼塊,就像我們使用npm一樣:

舉個(gè)例子抛寝,你有一家商店熊杨,然后你想給你的優(yōu)惠顧客10%的折扣:

function discount (price, discount) {
  return price * discount
}
// 當(dāng)一個(gè)顧客消費(fèi)了500元
const price = discount(500, 0.1) // $50

// 從長(zhǎng)遠(yuǎn)看,你的每一筆生意都要計(jì)算10%的折扣
const price = discount(1500, 0.1) // $150
const price = discount(2000, 0.1) // $200
const price = discount(50, 0.1) // $5
const price = discount(300, 0.1) // $30

// 將這個(gè)函數(shù)柯里化盗舰,然后我們就不用每次都寫(xiě)那0.1了
function discount (discount) {
  return (price) => {
    return price * discount
  }
}
const tenPercentDiscount = discount(0.1)

// 現(xiàn)在晶府,我們只需用商品價(jià)格來(lái)計(jì)算就可以了:
tenPercentDiscount(500) // $50

// 接下來(lái),有些優(yōu)惠顧客越來(lái)越重要钻趋,讓我們稱(chēng)為vip顧客川陆,然后我們要給20%的折扣,我們這樣來(lái)使用柯里化了的discount函數(shù):
const twentyPercentDiscount = discount(0.2)

// 我們?yōu)関ip顧客使用0.2調(diào)用柯里化discount函數(shù)來(lái)配置了一個(gè)新的函數(shù)蛮位。這個(gè)twentyPercentDiscount函數(shù)會(huì)被用來(lái)計(jì)算vip顧客的折扣:
twentyPercentDiscount(500) // $100
twentyPercentDiscount(3000) // $600
twentyPercentDiscount(80000) // $16000

這個(gè)例子說(shuō)明较沪,使用柯里化思想能讓我們?cè)谟龅街荒艽_定一個(gè)參數(shù)而無(wú)法確定另一個(gè)參數(shù)時(shí)鳞绕,代碼設(shè)計(jì)編的變得更方便與高效,達(dá)到提升性能的目的尸曼。

2.避免頻繁調(diào)用具有相同參數(shù)的函數(shù):

比如我們有個(gè)用來(lái)計(jì)算體積的函數(shù):

function volume (l, w, h) {
  return l * w * h
}

// 碰巧你倉(cāng)庫(kù)里的所有物品都是100m高们何。你會(huì)看到你不停地用h=100來(lái)調(diào)用這個(gè)函數(shù):
volume(200, 30, 100) // 2003000
volume(32, 45, 100) // 144000
volume(2322, 232, 100) // 53870400

// 為了解決這個(gè)問(wèn)題,你把volume函數(shù)柯里化(像我們之前做過(guò)的):
function volume (h) {
  return (w) => {
    return (l) => {
      return l * w * h
    }
  }
}

// 我們能給同類(lèi)物品定義一個(gè)特殊函數(shù):
const hCylinderHeight = volume(100)
hCylinderHeight(200)(30) // 600000
hCylinderHeight(2322)(232) // 53870400

通用的柯里函數(shù)

讓我們建立一個(gè)函數(shù)來(lái)接受任何函數(shù)并且返回柯里化版本的函數(shù):

function curry (fn, ...args) {
  return (..._args) => {
    return fn(...args, ..._args)
  }
}

我們?cè)谶@里做了什么控轿?我們的curry函數(shù)接受一個(gè)我們想要柯里化的函數(shù)(fn)和一個(gè)變量(...args)冤竹。這里的rest操作符用來(lái)將參數(shù)聚集成一個(gè)...args。接下來(lái)我們返回一個(gè)函數(shù)茬射,該函數(shù)將其余參數(shù)收集為...args鹦蠕。此函數(shù)通過(guò)spread運(yùn)算符將... args和... args作為參數(shù)解構(gòu)傳入來(lái)調(diào)用原始函數(shù)fn,然后將值返回給用戶(hù)在抛。

讓我們使用我們的curry函數(shù)用之前的例子來(lái)創(chuàng)建一個(gè)特殊的函數(shù)(一個(gè)專(zhuān)門(mén)用來(lái)計(jì)算100m長(zhǎng)度的物品體積):

function volume (l, h, w) {
  return l * h * w
}
const hCy = curry(volume, 100)
hCy(200, 900) // 18000000
hCy(70, 60) // 420000

將類(lèi)似回調(diào)函數(shù)的參數(shù)傳入柯里化函數(shù)钟病,能使復(fù)雜的問(wèn)題變得簡(jiǎn)單!

使用遞歸實(shí)現(xiàn)curry函數(shù)

JS柯里化作為函數(shù)式編程的重要一環(huán)霜定,頻繁在算法題中出現(xiàn)。以上的通用柯里化函數(shù)還不夠完善廊鸥,我們希望只給curry函數(shù)傳遞一個(gè)fn就能達(dá)到目的望浩,現(xiàn)在我們使用遞歸來(lái)實(shí)現(xiàn):

function curry (fn) {
  const c = (...args) => (args.length === fn.length) ?
          fn(...args) : (..._args) => c(...args, ..._args)
  return c
}

該方法幾乎為最簡(jiǎn)潔、代碼行數(shù)最少的實(shí)現(xiàn)方法了惰说。
首先我們能確定磨德,實(shí)現(xiàn)柯里化的核心就是要確定傳入?yún)?shù)的個(gè)數(shù),并通通取到吆视。
其次典挑,我們得知道,fn.length為fn函數(shù)接受的參數(shù)個(gè)數(shù)啦吧,那么該實(shí)現(xiàn)方法就能解讀為:
不斷遞歸獲取傳入?yún)?shù)您觉,直到取到的參數(shù)個(gè)數(shù)等于fn的參數(shù)個(gè)數(shù)為止,最終將獲取到的所有參數(shù)傳給fn并返回執(zhí)行結(jié)果授滓。

結(jié)語(yǔ)

柯里化作為一種重要的程序思想琳水,已經(jīng)廣為應(yīng)用,它使我們應(yīng)對(duì)復(fù)雜問(wèn)題時(shí)能提升效率般堆,增強(qiáng)可讀性在孝。希望讀者都能體會(huì)這種思想并運(yùn)用于實(shí)踐,相信在提升技術(shù)的路上能越走越遠(yuǎn)淮摔,成為一名優(yōu)秀的工程師私沮!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市和橙,隨后出現(xiàn)的幾起案子仔燕,更是在濱河造成了極大的恐慌造垛,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涨享,死亡現(xiàn)場(chǎng)離奇詭異筋搏,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)厕隧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)奔脐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人吁讨,你說(shuō)我怎么就攤上這事髓迎。” “怎么了建丧?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵排龄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我翎朱,道長(zhǎng)橄维,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任拴曲,我火速辦了婚禮争舞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘澈灼。我一直安慰自己竞川,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布叁熔。 她就那樣靜靜地躺著委乌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪荣回。 梳的紋絲不亂的頭發(fā)上遭贸,一...
    開(kāi)封第一講書(shū)人閱讀 51,521評(píng)論 1 304
  • 那天,我揣著相機(jī)與錄音心软,去河邊找鬼革砸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛糯累,可吹牛的內(nèi)容都是我干的算利。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼泳姐,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼效拭!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤缎患,失蹤者是張志新(化名)和其女友劉穎慕的,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體挤渔,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肮街,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了判导。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嫉父。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖眼刃,靈堂內(nèi)的尸體忽然破棺而出绕辖,到底是詐尸還是另有隱情,我是刑警寧澤擂红,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布仪际,位于F島的核電站,受9級(jí)特大地震影響昵骤,放射性物質(zhì)發(fā)生泄漏树碱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一变秦、第九天 我趴在偏房一處隱蔽的房頂上張望成榜。 院中可真熱鬧,春花似錦伴栓、人聲如沸伦连。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至额港,卻和暖如春饺窿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背移斩。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工肚医, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人向瓷。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓肠套,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親猖任。 傳聞我的和親對(duì)象是個(gè)殘疾皇子你稚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容