JavaScript 柯里化

簡(jiǎn)介

柯里化從何而來(lái)

柯里化, 即 Currying 的音譯厦坛。 Currying 是編譯原理層面實(shí)現(xiàn)多參函數(shù)的一個(gè)技術(shù)蹦漠。

在說(shuō)JavaScript 中的柯里化前,可以聊一下原始的Currying是什么息堂,又從何而來(lái)脐区。

在編碼過程中,身為碼農(nóng)的我們本質(zhì)上所進(jìn)行的工作就是——將復(fù)雜問題分解為多個(gè)可編程的小問題登澜。

Currying 為實(shí)現(xiàn)多參函數(shù)提供了一個(gè)遞歸降解的實(shí)現(xiàn)思路——把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù)阔挠,并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù),在某些編程語(yǔ)言中(如 Haskell)脑蠕,是通過 Currying 技術(shù)支持多參函數(shù)這一語(yǔ)言特性的购撼。

所以 Currying 原本是一門編譯原理層面的技術(shù),用途是實(shí)現(xiàn)多參函數(shù)谴仙。

柯里化去向哪里

在 Haskell 中迂求,函數(shù)作為一等公民,Currying 從編譯原理層面的技術(shù)應(yīng)運(yùn)而成了一個(gè)語(yǔ)言特性狞甚。 在語(yǔ)言特性層面锁摔,Currying 是什么?

在《Mostly adequate guide》一書中哼审,這樣總結(jié)了 Currying ——只傳遞給函數(shù)一部分參數(shù)來(lái)調(diào)用它谐腰,讓它返回一個(gè)函數(shù)去處理剩下的參數(shù)

所以 Currying 是應(yīng)函數(shù)式編程而生涩盾,在有了 Currying 后十气,大家再去探索去發(fā)掘了它的用途及意義。 然后因?yàn)檫@些用途和意義春霍,大家才積極地將它擴(kuò)展到其他編程語(yǔ)言中砸西。

在 JavaScript 中實(shí)現(xiàn) Currying

為了實(shí)現(xiàn)只傳遞給函數(shù)一部分參數(shù)來(lái)調(diào)用它,讓它返回一個(gè)函數(shù)去處理剩下的參數(shù)這句話所描述的特性址儒。 我們先寫一個(gè)實(shí)現(xiàn)加法的函數(shù) add

function add (x, y) {

  return (x + y)

}

現(xiàn)在我們直接實(shí)現(xiàn)一個(gè)被 Curryingadd 函數(shù)芹枷,該函數(shù)名為 curriedAdd,則根據(jù)上面的定義莲趣,curriedAdd 需要滿足以下條件:

curriedAdd(1)(3) === 4

// true

var increment = curriedAdd(1)

increment(2) === 3

// true

var addTen = curriedAdd(10)

addTen(2) === 12

// true

滿足以上條件的 curriedAdd 的函數(shù)可以用以下代碼段實(shí)現(xiàn):

function curriedAdd (x) {

  return function(y) {

    return x + y

  }
}

當(dāng)然以上實(shí)現(xiàn)是有一些問題的:它并不通用鸳慈,并且我們并不想通過重新編碼函數(shù)本身的方式來(lái)實(shí)現(xiàn) Currying 化。

但是這個(gè) curriedAdd 的實(shí)現(xiàn)表明了實(shí)現(xiàn) Currying 的一個(gè)基礎(chǔ) —— Currying 延遲求值的特性需要用到 JavaScript 中的作用域——說(shuō)得更通俗一些喧伞,我們需要使用作用域來(lái)保存上一次傳進(jìn)來(lái)的參數(shù)走芋。

對(duì) curriedAdd 進(jìn)行抽象,可能會(huì)得到如下函數(shù) currying


function currying (fn, ...args1) {

    return function (...args2) {

        return fn(...args1, ...args2)

    }
}

var increment = currying(add, 1)

increment(2) === 3

// true

var addTen = currying(add, 10)

addTen(2) === 12

// true

在此實(shí)現(xiàn)中潘鲫,currying 函數(shù)的返回值其實(shí)是一個(gè)接收剩余參數(shù)并且立即返回計(jì)算值的函數(shù)翁逞。即它的返回值并沒有自動(dòng)被 Currying化 。所以我們可以通過遞歸來(lái)將 currying 的返回的函數(shù)也自動(dòng) Currying 化溉仑。

function trueCurrying(fn, ...args) {

    if (args.length >= fn.length) {

        return fn(...args)

    }

    return function (...args2) {

        return trueCurrying(fn, ...args, ...args2)

    }
}

以上函數(shù)很簡(jiǎn)短挖函,但是已經(jīng)實(shí)現(xiàn) Currying 的核心思想了。JavaScript 中的常用庫(kù) Lodash 中的 curry 方法彼念,其核心思想和以上并沒有太大差異——比較多次接受的參數(shù)總數(shù)與函數(shù)定義時(shí)的入?yún)?shù)量挪圾,當(dāng)接受參數(shù)的數(shù)量大于或等于被 Currying 函數(shù)的傳入?yún)?shù)數(shù)量時(shí)浅萧,就返回計(jì)算結(jié)果,否則返回一個(gè)繼續(xù)接受參數(shù)的函數(shù)哲思。

Lodash 中實(shí)現(xiàn) Currying 的代碼段較長(zhǎng)洼畅,因?yàn)樗紤]了更多的事情,比如綁定 this 變量等棚赔。在此處就不直接貼出 Lodash 中的代碼段帝簇,感興趣的同學(xué)可以去看看看 Lodash 源碼,比較一下這兩種實(shí)現(xiàn)會(huì)導(dǎo)致什么樣的差異靠益。

然而 Currying 的定義和實(shí)現(xiàn)都不是最重要的丧肴,本文想要闡述的重點(diǎn)是:它能夠解決編碼和開發(fā)當(dāng)中怎樣的問題,以及在面對(duì)不同的問題時(shí)胧后,選擇一個(gè)合適的 Currying芋浮,來(lái)最恰當(dāng)?shù)慕鉀Q問題

Currying 使用場(chǎng)景

參數(shù)復(fù)用

固定不變的參數(shù)壳快,實(shí)現(xiàn)參數(shù)復(fù)用是 Currying 的主要用途之一纸巷。

上文中的increment, addTen是一個(gè)參數(shù)復(fù)用的實(shí)例。對(duì)add方法固定第一個(gè)參數(shù)為 10 后眶痰,改方法就變成了一個(gè)將接受的變量值加 10 的方法瘤旨。

延遲執(zhí)行

延遲執(zhí)行也是 Currying 的一個(gè)重要使用場(chǎng)景,同樣 bind 和箭頭函數(shù)也能實(shí)現(xiàn)同樣的功能竖伯。

在前端開發(fā)中存哲,一個(gè)常見的場(chǎng)景就是為標(biāo)簽綁定 onClick 事件,同時(shí)考慮為綁定的方法傳遞參數(shù)七婴。

以下列出了幾種常見的方法祟偷,來(lái)比較優(yōu)劣:

  1. 通過 data 屬性

    <div data-name="name" onClick={handleOnClick} />
    
    

    通過 data 屬性本質(zhì)只能傳遞字符串的數(shù)據(jù),如果需要傳遞復(fù)雜對(duì)象打厘,只能通過 JSON.stringify(data) 來(lái)傳遞滿足 JSON 對(duì)象格式的數(shù)據(jù)肩袍,但對(duì)更加復(fù)雜的對(duì)象無(wú)法支持。(雖然大多數(shù)時(shí)候也無(wú)需傳遞復(fù)雜對(duì)象)

  2. 通過bind方法

    <div onClick={handleOnClick.bind(null, data)} />
    
    

    bind 方法和以上實(shí)現(xiàn)的 currying 方法婚惫,在功能上有極大的相似,在實(shí)現(xiàn)上也幾乎差不多魂爪∠认希可能唯一的不同就是 bind 方法需要強(qiáng)制綁定 context,也就是 bind 的第一個(gè)參數(shù)會(huì)作為原函數(shù)運(yùn)行時(shí)的 this 指向滓侍。而 currying 不需要此參數(shù)蒋川。所以使用 currying 或者 bind 只是一個(gè)取舍問題。

  3. 箭頭函數(shù)

    <div onClick={() => handleOnClick(data))} />
    
    

    箭頭函數(shù)能夠?qū)崿F(xiàn)延遲執(zhí)行撩笆,同時(shí)也不像 bind 方法必需指定 context捺球「灼郑可能唯一需要顧慮的就是在 react 中,會(huì)有人反對(duì)在 jsx 標(biāo)簽內(nèi)寫箭頭函數(shù)氮兵,這樣子容易導(dǎo)致直接在 jsx 標(biāo)簽內(nèi)寫業(yè)務(wù)邏輯裂逐。

  4. 通過currying

    <div onClick={currying(handleOnClick, data)} />
    
    

性能對(duì)比

image.png

通過 jsPerf 測(cè)試四種方式的性能,結(jié)果為:箭頭函數(shù)>bind>currying>trueCurrying泣栈。

currying 函數(shù)相比 bind 函數(shù)卜高,其原理相似,但是性能相差巨大南片,其原因是 bind 由瀏覽器實(shí)現(xiàn)掺涛,運(yùn)行效率有加成。

從這個(gè)結(jié)果看 Currying 性能無(wú)疑是最差的疼进,但是另一方面就算最差的 trueCurrying 的實(shí)現(xiàn)薪缆,也能在本人的個(gè)人電腦上達(dá)到 50w Ops/s 的情況下,說(shuō)明這些性能是無(wú)需在意的伞广。

trueCurrying 方法中實(shí)現(xiàn)的自動(dòng) Currying 化拣帽,是另外三個(gè)方法所不具備的。

到底需不需要 Currying

為什么需要 Currying

  1. 為了多參函數(shù)復(fù)用性

    Currying 讓人眼前一亮的地方在于赔癌,讓人覺得函數(shù)還能這樣子復(fù)用诞外。

    通過一行代碼,將 add 函數(shù)轉(zhuǎn)換為 increment灾票,addTen 等峡谊。

    對(duì)于 Currying 的復(fù)雜實(shí)現(xiàn)中,以 Lodash 為列刊苍,提供了 placeholder 的神奇操作既们。對(duì)多參函數(shù)的復(fù)用玩出花樣。

    import _ from 'loadsh'
    
    function abc (a, b, c) {
      return [a, b, c];
    }
    
    var curried = _.curry(abc)
    
    // Curried with placeholders.
    curried(1)(_, 3)(2)
    // => [1, 2, 3]
    
    
  2. 為函數(shù)式編程而生

    Currying 是為函數(shù)式而生的東西正什。應(yīng)運(yùn)著有一整套函數(shù)式編程的東西啥纸,純函數(shù)compose婴氮、container等等事物斯棒。(可閱讀《mostly-adequate-guide》

    假如要寫 Pointfree Javascript 風(fēng)格的代碼,那么Currying是不可或缺的主经。

    要使用 compose荣暮,要使用 container 等事物,我們也需要 Currying罩驻。

為什么不需要 Currying

  1. Currying 的一些特性有其他解決方案

    如果我們只是想提前綁定參數(shù)穗酥,那么我們有很多好幾個(gè)現(xiàn)成的選擇,bind,箭頭函數(shù)等砾跃,而且性能比Curring更好骏啰。

  2. Currying 陷于函數(shù)式編程

    在本文中,提供了一個(gè) trueCurrying 的實(shí)現(xiàn)抽高,這個(gè)實(shí)現(xiàn)也是最符合 Currying 定義的判耕,也提供 了bind,箭頭函數(shù)等不具備的“新奇”特性——可持續(xù)的 Currying(這個(gè)詞是本人臨時(shí)造的)厨内。

    但是這個(gè)“新奇”特性的應(yīng)用并非想象得那么廣泛祈秕。

    其原因在于,Currying 是函數(shù)式編程的產(chǎn)物雏胃,它生于函數(shù)式編程请毛,也服務(wù)于函數(shù)式編程。

    而 JavaScript 并非真正的函數(shù)式編程語(yǔ)言瞭亮,相比 Haskell 等函數(shù)式編程語(yǔ)言方仿,JavaScript 使用 Currying 等函數(shù)式特性有額外的性能開銷,也缺乏類型推導(dǎo)统翩。

    從而把 JavaScript 代碼寫得符合函數(shù)式編程思想和規(guī)范的項(xiàng)目都較少仙蚜,從而也限制了 Currying 等技術(shù)在 JavaScript 代碼中的普遍使用。

    假如我們還沒有準(zhǔn)備好去寫函數(shù)式編程規(guī)范的代碼厂汗,僅需要在 JSX 代碼中提前綁定一次參數(shù)委粉,那么 bind 或箭頭函數(shù)就足夠了。

結(jié)論

  1. Currying 在 JavaScript 中是“低性能”的娶桦,但是這些性能在絕大多數(shù)場(chǎng)景贾节,是可以忽略的。
  2. Currying 的思想極大地助于提升函數(shù)的復(fù)用性衷畦。
  3. Currying 生于函數(shù)式編程栗涂,也陷于函數(shù)式編程。假如沒有準(zhǔn)備好寫純正的函數(shù)式代碼祈争,那么 Currying 有更好的替代品斤程。
  4. 函數(shù)式編程及其思想,是值得關(guān)注菩混、學(xué)習(xí)和應(yīng)用的事物忿墅。所以在文末再次安利 JavaScript 程序員閱讀此書 —— 《mostly-adequate-guide》

參考鏈接

https://juejin.im/post/5af13664f265da0ba266efcf?utm_source=gold_browser_extension

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市沮峡,隨后出現(xiàn)的幾起案子球匕,更是在濱河造成了極大的恐慌,老刑警劉巖帖烘,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異橄杨,居然都是意外死亡秘症,警方通過查閱死者的電腦和手機(jī)照卦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)乡摹,“玉大人役耕,你說(shuō)我怎么就攤上這事〈狭” “怎么了瞬痘?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)板熊。 經(jīng)常有香客問我框全,道長(zhǎng),這世上最難降的妖魔是什么干签? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任津辩,我火速辦了婚禮,結(jié)果婚禮上容劳,老公的妹妹穿的比我還像新娘喘沿。我一直安慰自己,他們只是感情好竭贩,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布蚜印。 她就那樣靜靜地躺著,像睡著了一般留量。 火紅的嫁衣襯著肌膚如雪窄赋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天肪获,我揣著相機(jī)與錄音寝凌,去河邊找鬼。 笑死孝赫,一個(gè)胖子當(dāng)著我的面吹牛较木,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播青柄,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼伐债,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了致开?” 一聲冷哼從身側(cè)響起峰锁,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎双戳,沒想到半個(gè)月后虹蒋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年魄衅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了峭竣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡晃虫,死狀恐怖皆撩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情哲银,我是刑警寧澤扛吞,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站荆责,受9級(jí)特大地震影響滥比,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜草巡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一守呜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧山憨,春花似錦查乒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至棚亩,卻和暖如春蓖议,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背讥蟆。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工勒虾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瘸彤。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓修然,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親质况。 傳聞我的和親對(duì)象是個(gè)殘疾皇子愕宋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348