JS的柯里化與偏應用

在之前的文章中對函數式編程做了一個簡單的概述,在這篇文章中對一個大家喜聞樂見的話題——函數的柯里化進行一個總結。

** ⚠️注意: ** 柯里化和偏應用的概念經常被混用,在文中會有概念上的簡單區分

一些基本概念

一元函數、多元函數以及變參函數

這些概念還是很好理解的,我們的日常開發中總是伴隨着這些函數:

  • 一元函數:只有一個參數的函數,形如
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函數預先提供參數")});
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章