在之前的文章中對函數式編程做了一個簡單的概述,在這篇文章中對一個大家喜聞樂見的話題——函數的柯里化進行一個總結。
** ⚠️注意: ** 柯里化和偏應用的概念經常被混用,在文中會有概念上的簡單區分
一些基本概念
一元函數、多元函數以及變參函數
這些概念還是很好理解的,我們的日常開發中總是伴隨着這些函數:
- 一元函數:只有一個參數的函數,形如
let log = (msg) => {console.log(msg)}
- 多元函數:有多個參數的函數,形如
let add = (x,y) => {return x+y}
- 變參函數:參數數量不確定的函數,在ES6之前我們通過
argements
獲取所有參數,現在我們往往會使用...args
因爲這樣我們可以直接使用數組方法進行操作,形如
function logAll(){
// arguments 不是數組,只是一個類數組對象
console.log(arguments);
}
logAll(1,2,3); // [1,2,3]
function logAllByES6(...arg){
// arg是數組,可以執行數組方法
console.log(arg.map((item)=>{return item*2}));
}
logAllByES6(1,2,3) // [2,4,6]
柯里化
根據一個相對通用的定義,函數的柯里化指:
把一個多參數函數轉換爲一個嵌套的一元函數的過程
有一個非常典型的例子是add函數
// 柯里化前
function add(x,y){
return x+y;
}
add(1,2); // 3
// 柯里化後
function addCurried(x){
return function(y){
return x+y;
}
}
addCurried(1)(2); //3
這樣做的好處在於我們可以通過這樣的方式得到一系列新函數,從而讓我們優化數據的處理過程
偏應用(部分應用)
偏應用又稱作部分應用,偏應用的概念和柯里化有所區別,但是又很類似
- 柯里化將函數轉化爲嵌套的一元函數
- 偏應用是爲一個多元函數預先提供部分參數,從而在調用時可以省略這些參數
事實上有很多文章中的柯里化指的就是偏應用,比如:
function add(x,y,z){
return x+y+z;
}
add(1,2,3); // 6
// 柯里化
addcurried(1)(2)(3); // 6
// 偏應用
addPartial(1,2)(3); // 6
addPartial(1)(2,3); // 6
實現
實現的方式多種多樣,寫這篇文章的時候也參考了很多前輩的文章,有各種版本的實現,在ES6
的加持下還出現了“一行代碼實現柯里化”這樣的騷操作
不過在這裏,我選擇了比較容易被大家接受的實現方法記錄在文中
以下實現方法參考了 Anto Aravinth
的實現,並做了一點小小的改動
let curry = (fn) => {
return function curriedFn(...args){
if(args.length < fn.length){
return function(...args2){
return curriedFn.apply(null,args.concat(args2));
}
}
return fn.apply(null,args);
}
}
let tipFunc = () => {console.log("通過柯里化,令 tipFunc 在指定事件後執行")}
let tipsTimer = curry(setTimeout)(tipFunc);
tipsTimer(10000); // 10s 後執行
tipsTimer(20000); // 20s 後執行
// 偏應用函數
let partial = (fn,...partialArgs)=>{
let args = partialArgs;
return function(...fullArgs){
let arg = 0;
for(let i = 0;i < args.length && arg < fullArgs.length;i++){
if (args[i] === undefined){
args[i] = fullArgs[arg++];
}
}
return fn.apply(null,args);
}
}
let timer10s = partial(setTimeout,undefined,10000);
partial(()=>{console.log("通過偏應用,爲setTimeout函數預先提供參數")});