前端戰五渣學JavaScript——函式柯里化

戈德斯文發表於2019-08-21

閱讀本篇部落格之前需要對JavaScript的閉包有一個很好的瞭解,可以看一下我之前寫的《前端戰五渣學JavaScript——閉包》

我自認為大家是在瞭解閉包的情況下閱讀這篇部落格的。

最近對什麼都提不起興趣,什麼都不想幹,什麼都不想學,遊戲也不想打。。。哎。。但是最近看了一部國產動畫電影《哪吒之魔童降世》,感覺現在國產動畫的水平真是越來越高了,很不錯,值得一看哦

什麼是函式柯里化

前端戰五渣學JavaScript——函式柯里化

函式柯里化(function currying),把函式做成咖哩味的??其實我初次聽說這個詞的時候,感覺好高大上啊,音譯,深奧,等我翻看了相關書籍(《JavaScript高階程式設計》)以及一些前輩寫的部落格以後,發現原來函式柯里化就是使用閉包返回一個函式

柯里化的函式可以延遲接收引數,就是比如我一個函式需要接收的引數是兩個,我執行的時候必須接收兩個引數,否則我沒法執行啊,是不是,就容易出問題。但是柯里化後的函式,可以先接收一個函式,然後再接收一個函式,這麼說太生硬了,那我們就來看一個簡單的例子(也是全網最普遍的例子)⬇️

這是普通的函式

// 正常我們宣告函式的時候是這樣的
function 求兩數之和(第一個數, 第二個數) {
  return 第一個數 + 第二個數
}
const 和 = 求兩數之和(1, 2);
console.log(`兩數之和為 ${和}`); // 3
複製程式碼

前端戰五渣學JavaScript——函式柯里化
那我們按照柯里化寫出來的函式是什麼樣的呢

// 理解柯里化的思想
function 求兩數之和(第一個數) {
  return function (第二個數) {
    return 第一個數 + 第二個數
  }
}
// 第一種方法
const 和 = 求兩數之和(1)(2);
console.log(`第一種方法的兩個數之和為 ${和}`);
// 第二種方法
const 已接收第一個數的求和函式 = 求兩數之和(1);
const 兩個數都已接收的和 = 已接收第一個數的求和函式(2);
console.log(`第二種方法的兩個數之和為 ${兩個數都已接收的和}`);
複製程式碼

前端戰五渣學JavaScript——函式柯里化

從上面的函式對比我們就可以發現,柯里化的函式可以先接收一個函式,然後在我們需要的傳入第二個引數的時候,我們再傳入,並執行最終的結果。

所以我們可以動態生成比如第一個引數相同,第二個引數不同,或者第二個引數相同,第一個引數不同的函式

把函式柯里化的函式

在我們熟知的《JavaScript高階程式設計》中給出了一個方法,這個方法,可以讓我們把一個本來不支援柯里化的函式,轉化成支援柯里化思想的函式。

function 把函式柯里化(需要柯里化的函式) {
  const 呼叫柯里化時除了函式以外的引數 = Array.prototype.slice.call(arguments, 1);
  return function () {
    const 後接受的引數 = [...arguments];
    const 最終的引數 = [...呼叫柯里化時除了函式以外的引數, ...後接受的引數];
    return 需要柯里化的函式.apply(null, 最終的引數);
  }
}

function 求兩數之和(第一個數, 第二個數) {
  return 第一個數 + 第二個數
}

const 柯里化的求兩數之和 = 把函式柯里化(求兩數之和, 1);

console.log(
  `被柯里化後的函式求的兩數之和為 ${
    柯里化的求兩數之和(2)
  }`
); // 被柯里化後的函式求的兩數之和為 3
複製程式碼

我們發現,我們可以單獨寫一個函式,然後把一個普通函式轉化成支援柯里化的函式,但是高階程式設計上提供的這個方法,只能提供兩次呼叫,什麼意思?我們來看看lodashcurry方法是如何實現的

const _ = require('lodash');

function 求三個數之和(第一個數, 第二個數, 第三個數) {
  return 第一個數 + 第二個數 + 第三個數
}

const 柯里化後的求三個數之和 = _.curry(求三個數之和);

console.log(柯里化後的求三個數之和(1)(2)(3)); // 6
console.log(柯里化後的求三個數之和(1)(2, 3)); // 6
console.log(柯里化後的求三個數之和(1, 2, 3)); // 6
複製程式碼

這下我們看到,lodash中對轉化柯里化的處理,我們可以任意呼叫被柯里化的函式,不管引數怎麼傳,只要傳夠三個就行,也不管是分幾次傳。

我們自己來一個柯里化函式吧

我們的需求是什麼?

feature

  1. 需要把普通函式轉化成柯里函式
  2. 只要引數沒達到被轉換函式的數量,就返回函式,儲存已傳引數
  3. 支援引數沒達到被轉換函式的數量時,無限呼叫

那我們先來看看我們需要轉換的函式⬇️

const 封神榜 = (名字, 寶物, 出生地) => console.log(`我是${出生地}${名字},我有${寶物}`);

封神榜('哪吒', '乾坤圈', '陳塘關'); // 我是陳塘關的哪吒,我有乾坤圈
複製程式碼

看看我們普通轉化的結果是什麼樣的⬇️

const 封神榜 = 名字 => 寶物 => 出生地 => console.log(`我是${出生地}${名字},我有${寶物}`);

封神榜('哪吒')('乾坤圈')('陳塘關');
複製程式碼

普通轉化的函式,我們傳參不是很自由,必須穿三次,每次傳一個,那我們來個牛逼的⬇️

const 封神榜 = (名字, 寶物, 出生地) => console.log(`我是${出生地}${名字},我有${寶物}`);

const 柯里化 = (需要柯里化的函式, ...引數) => 需要柯里化的函式.length <= 引數.length
  ? 需要柯里化的函式(...引數)
  : (...更多引數) => 柯里化(需要柯里化的函式, ...引數, ...更多引數);

const 柯里化後的封神榜 = 柯里化(封神榜);

柯里化後的封神榜('哪吒', '乾坤圈', '陳塘關'); // 我是陳塘關的哪吒,我有乾坤圈
柯里化後的封神榜('孫悟空')('金箍魯棒')('花果山'); // 我是花果山的孫悟空,我有金箍魯棒
柯里化後的封神榜('雷震子', '黃金棍')('終南山玉柱洞'); // 我是終南山玉柱洞的雷震子,我有黃金棍
柯里化後的封神榜()()()()()()()()('姜子牙', '打神鞭', '崑崙山'); // 我是崑崙山的姜子牙,我有打神鞭
複製程式碼

前端戰五渣學JavaScript——函式柯里化

看,現在我們實現的柯里化函式已經基本滿足我們剛才提的需求了

當然,有柯里化就有反柯里化,但是我覺得沒什麼必要,我既然都已經柯里化了,柯里化後的函式當然支援非柯里化函式的傳參方式,但是有興趣的小夥伴還是可以瞭解一下的

總結

其實函式柯里化這個概念,可能我們之前沒有聽說過,但是工作或者學習中我們可能或多或少接觸過這種方式,只是不知道這麼寫就是柯里化,這個函式就是柯里化函式。

柯里化函式這種方式方法還是很實用的,在日常開發過程中還是會遇到需要這種需求的地方。


我是前端戰五渣,一個前端界的小學生。

相關文章