掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
柯里化(Currying)

創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供烏翠網(wǎng)站建設(shè)、烏翠做網(wǎng)站、烏翠網(wǎng)站設(shè)計(jì)、烏翠網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)與制作、烏翠企業(yè)網(wǎng)站模板建站服務(wù),10年烏翠做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。
柯里化(Currying)[1]是一種關(guān)于函數(shù)的高階技術(shù)。它不僅被用于 JavaScript,還被用于其他編程語(yǔ)言。
柯里化是一種函數(shù)的轉(zhuǎn)換,它是指將一個(gè)函數(shù)從可調(diào)用的 f(a, b, c) 轉(zhuǎn)換為可調(diào)用的 f(a)(b)(c)。
柯里化不會(huì)調(diào)用函數(shù)。它只是對(duì)函數(shù)進(jìn)行轉(zhuǎn)換。
讓我們先來看一個(gè)例子,以更好地理解我們正在講的內(nèi)容,然后再進(jìn)行一個(gè)實(shí)際應(yīng)用。
我們將創(chuàng)建一個(gè)輔助函數(shù) curry(f),該函數(shù)將對(duì)兩個(gè)參數(shù)的函數(shù) f 執(zhí)行柯里化。換句話說,對(duì)于兩個(gè)參數(shù)的函數(shù) f(a, b) 執(zhí)行 curry(f) 會(huì)將其轉(zhuǎn)換為以 f(a)(b) 形式運(yùn)行的函數(shù):
- function curry(f) { // curry(f) 執(zhí)行柯里化轉(zhuǎn)換
- return function(a) {
- return function(b) {
- return f(a, b);
- };
- };
- }
- // 用法
- function sum(a, b) {
- return a + b;
- }
- let curriedSum = curry(sum);
- alert( curriedSum(1)(2) ); // 3
正如你所看到的,實(shí)現(xiàn)非常簡(jiǎn)單:只有兩個(gè)包裝器(wrapper)。
柯里化更高級(jí)的實(shí)現(xiàn),例如 lodash 庫(kù)的 _.curry[2],會(huì)返回一個(gè)包裝器,該包裝器允許函數(shù)被正常調(diào)用或者以偏函數(shù)(partial)的方式調(diào)用:
- function sum(a, b) {
- return a + b;
- }
- let curriedSum = _.curry(sum); // 使用來自 lodash 庫(kù)的 _.curry
- alert( curriedSum(1, 2) ); // 3,仍可正常調(diào)用
- alert( curriedSum(1)(2) ); // 3,以偏函數(shù)的方式調(diào)用
柯里化?目的是什么?
要了解它的好處,我們需要一個(gè)實(shí)際中的例子。
例如,我們有一個(gè)用于格式化和輸出信息的日志(logging)函數(shù) log(date, importance, message)。在實(shí)際項(xiàng)目中,此類函數(shù)具有很多有用的功能,例如通過網(wǎng)絡(luò)發(fā)送日志(log),在這兒我們僅使用 alert:
- function log(date, importance, message) {
- alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
- }
讓我們將它柯里化!
- log = _.curry(log);
柯里化之后,log 仍正常運(yùn)行:
- log(new Date(), "DEBUG", "some debug"); // log(a, b, c)
……但是也可以以柯里化形式運(yùn)行:
- log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)
現(xiàn)在,我們可以輕松地為當(dāng)前日志創(chuàng)建便捷函數(shù):
- // logNow 會(huì)是帶有固定第一個(gè)參數(shù)的日志的偏函數(shù)
- let logNow = log(new Date());
- // 使用它
- logNow("INFO", "message"); // [HH:mm] INFO message
現(xiàn)在,logNow 是具有固定第一個(gè)參數(shù)的 log,換句話說,就是更簡(jiǎn)短的“偏應(yīng)用函數(shù)(partially applied function)”或“偏函數(shù)(partial)”。
我們可以更進(jìn)一步,為當(dāng)前的調(diào)試日志(debug log)提供便捷函數(shù):
- let debugNow = logNow("DEBUG");
- debugNow("message"); // [HH:mm] DEBUG message
所以:
高級(jí)柯里化實(shí)現(xiàn)
如果你想了解更多細(xì)節(jié),下面是用于多參數(shù)函數(shù)的“高級(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);
- alert( curriedSum(1, 2, 3) ); // 6,仍然可以被正常調(diào)用
- alert( curriedSum(1)(2,3) ); // 6,對(duì)第一個(gè)參數(shù)的柯里化
- alert( curriedSum(1)(2)(3) ); // 6,全柯里化
新的 curry 可能看上去有點(diǎn)復(fù)雜,但是它很容易理解。
curry(func) 調(diào)用的結(jié)果是如下所示的包裝器 curried:
- // func 是要轉(zhuǎn)換的函數(shù)
- 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è) if 執(zhí)行分支:
例如,讓我們看看 sum(a, b, c) 這個(gè)例子。它有三個(gè)參數(shù),所以 sum.length = 3。
對(duì)于調(diào)用 curried(1)(2)(3):
如果這還不夠清楚,那你可以把函數(shù)調(diào)用順序在你的腦海中或者在紙上過一遍。
只允許確定參數(shù)長(zhǎng)度的函數(shù)
柯里化要求函數(shù)具有固定數(shù)量的參數(shù)。
使用 rest 參數(shù)的函數(shù),例如 f(...args),不能以這種方式進(jìn)行柯里化。
比柯里化多一點(diǎn)
根據(jù)定義,柯里化應(yīng)該將 sum(a, b, c) 轉(zhuǎn)換為 sum(a)(b)(c)。
但是,如前所述,JavaScript 中大多數(shù)的柯里化實(shí)現(xiàn)都是高級(jí)版的:它們使得函數(shù)可以被多參數(shù)變體調(diào)用。
總結(jié)
柯里化 是一種轉(zhuǎn)換,將 f(a,b,c) 轉(zhuǎn)換為可以被以 f(a)(b)(c) 的形式進(jìn)行調(diào)用。JavaScript 實(shí)現(xiàn)通常都保持該函數(shù)可以被正常調(diào)用,并且如果參數(shù)數(shù)量不足,則返回偏函數(shù)。
柯里化讓我們能夠更容易地獲取偏函數(shù)。就像我們?cè)谌罩居涗浭纠锌吹降哪菢樱胀ê瘮?shù)log(date, importance, message) 在被柯里化之后,當(dāng)我們調(diào)用它的時(shí)候傳入一個(gè)參數(shù)(如log(date))或兩個(gè)參數(shù)(log(date, importance))時(shí),它會(huì)返回偏函數(shù)。
現(xiàn)代 JavaScript 教程:開源的現(xiàn)代 JavaScript 從入門到進(jìn)階的優(yōu)質(zhì)教程。React 官方文檔推薦,與 MDN 并列的 JavaScript 學(xué)習(xí)教程[3]。
在線免費(fèi)閱讀:https://zh.javascript.info
參考資料
[1]柯里化(Currying): https://en.wikipedia.org/wiki/Currying
[2]_.curry: https://lodash.com/docs#curry
[3]React 官方文檔推薦,與 MDN 并列的 JavaScript 學(xué)習(xí)教程: https://zh-hans.reactjs.org/docs/getting-started.html#javascript-resources
本文轉(zhuǎn)載自微信公眾號(hào)「技術(shù)漫談」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系技術(shù)漫談公眾號(hào)。

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流