柯里化

柯里化

高階函數

在說明柯里化以前,首先須要理解高階函數的定義javascript

高階函數是指以函數做爲參數的函數,僞代碼能夠理解爲java

function higherOrderFunction(fn) {
    console.log(typeof fn) // "function"
    
}

定義

在計算機科學中,柯里化(Currying)是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數且返回結果的新函數的技術。數組

從定義中咱們能夠對柯里化的步驟作一個簡要的歸納:閉包

  1. 存在一個函數currying,接受一個函數source做爲參數,並返回一個函數tmpCurrying。
  2. tmpCurrying接收單一參數,並再次返回一個tmpCurrying,直到全部tmpCurrying接收的參數和等於source函數所需的形參數量。
  3. 將tmpCurrying收到的全部單一參數按順序放入source函數,並執行,以得到結果。

實際應用

使用形式

根據如上定義,能夠用以下僞碼錶示柯里化的使用app

  1. 參數分步輸入
// 實現參數分步輸入
function sum(a,b,c) {
    return [...args].reduce((pre,next) => (pre + next));
}

// 存在一個函數currying

const curriedSum = currying(sum);

curriedSum(1)(2)(3); // 6;
curriedSum(1, 2)(3); // 6;
curriedSum(1, 2, 3); // 6;
  1. 函數抽象,高階函數封裝
// 用於函數抽象,高階函數封裝等
// 存在以下功能函數

function isPhone(number) {
    return /^1[34578]\d{9}$/.test(number);
}

function isMail(mail) {
    return /^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/.test(mail);
}

/*
    能夠講上面兩個函數抽象爲
    regString.test(targetString);
*/
function check(reg, target) {
    return reg.test(target);
}


/*
    可是每次使用時仍然須要輸入正則做爲參數,因而考慮利用柯里化的功能,將函數參數拆爲兩部分,正則 + 校驗對象
    假設存在一個柯里化函數currying(fn, reg)
*/



export const checkPhone = currying(check, /^1[34578]\d{9}$/);

export const checkMail = currying(check, /^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);


/* 
    checkPhone和checkMail此時皆是隻須要一個參數targetString的函數
    使用時只需直接使用便可
*/

checkPhone(13111111111); // true;

柯里化實現

想要實現柯里化函數,須要掌握如下知識點函數

  1. 閉包:內層函數能夠訪問外層函數的變量
  2. Function.length: 函數的length屬性表示其聲明時的形參數量
  3. arguments: 類數組arguments表示函數調用時的實參列表(或直接使用參數解構,獲取實參數組,推薦此種。緣由:arguments只是類數組,沒有數組方法,不方便使用,須要用結構或apply等方式將其轉化爲數組)

實現解析

  1. 利用閉包將每次單獨輸入的參數存入外層函數currying的數組變量args中。
  2. 校驗當前args的長度與被封裝函數的形參數量是否相等,不相等則繼續返回接受參數的中間函數。
  3. 若相等,則將參數放入源函數並返回執行結果。

實現1---每次只接受一個參數

function currying(src) {
    // 記錄源函數的形參長度
    const length = src.length;
    // 參數列表
    const argsPool = [];
    
    return function tmpFn (arg) {
        // 將參數推入參數池
        argsPool.push(arg);
        
        // 長度判斷
        if (length > argsPool.length) {
            return tmpFn;
        } else {
            const res = src(...argsPool);
            argsPool = [];
            return res;
        }
    }
}


function sum(a, b, c, d, e, f) {
    return [...arguments].reduce((pre, next) => (pre + next));
}


const _sum = currying(sum);
_sum(1)(2)(3)(4)(5)(6); // 21

實現2:每次接受若干個參數

function currying(src, ...args) {
    // 記錄源函數的形參長度
    const length = src.length;
    // 參數列表
    const argsPool = [...args];
    
    return function tmpFn (...args) {
        // 將參數推入參數池
        argsPool.push(...args);
        
        // 長度判斷
        if (length > argsPool.length) {
            return tmpFn;
        } else {
            const res = src(...argsPool);
            argsPool = [];
            return res;
        }
    }
}


function sum(a, b, c, d, e, f) {
    return [...arguments].reduce((pre, next) => (pre + next));
}


const _sum = currying(sum);
_sum(1)(2)(3)(4)(5)(6); // 21
_sum(1, 2, 3, 4)(5, 6); // 21

實現3:不規定參數個數,以無參數傳入爲循環終止標識

function currying(src, ...args) {
    // 參數列表
    let argsPool = [...args];
    
    
    return function tmpFn (...args) {
        if (args.length > 0) {
            argsPool.push(...args);
            return tmpFn;
        } else {
            const res = src(...argsPool);
            argsPool = [];
            return res;
        }
    }
}


function sum(...args) {
    return args.reduce((pre, next) => (pre + next));
}


const _sum = currying(sum);

_sum(1,2,3)(4,5)(); // 15
_sum(1,2)();        // 3
相關文章
相關標籤/搜索