柯里化
高階函數
在説明柯里化之前,首先需要理解高階函數的定義
高階函數是指以函數作為參數的函數,偽代碼可以理解為
function higherOrderFunction(fn) {
console.log(typeof fn) // "function"
}
定義
在計算機科學中,柯里化(Currying)是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受餘下的參數且返回結果的新函數的技術。
從定義中我們可以對柯里化的步驟做一個簡要的概括:
- 存在一個函數currying,接受一個函數source作為參數,並返回一個函數tmpCurrying。
- tmpCurrying接收單一參數,並再次返回一個tmpCurrying,直到所有tmpCurrying接收的參數和等於source函數所需的形參數量。
- 將tmpCurrying收到的所有單一參數按順序放入source函數,並執行,以獲得結果。
實際應用
使用形式
根據如上定義,可以用如下偽碼錶示柯里化的使用
- 參數分步輸入
// 實現參數分步輸入
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;
- 函數抽象,高階函數封裝
// 用於函數抽象,高階函數封裝等
// 存在如下功能函數
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;
柯里化實現
想要實現柯里化函數,需要掌握以下知識點
- 閉包:內層函數可以訪問外層函數的變量
- Function.length: 函數的length屬性表示其聲明時的形參數量
- arguments: 類數組arguments表示函數調用時的實參列表(或直接使用參數解構,獲取實參數組,推薦此種。原因:arguments只是類數組,沒有數組方法,不方便使用,需要用結構或apply等方式將其轉化為數組)
實現解析
- 利用閉包將每次單獨輸入的參數存入外層函數currying的數組變量args中。
- 校驗當前args的長度與被封裝函數的形參數量是否相等,不相等則繼續返回接受參數的中間函數。
- 若相等,則將參數放入源函數並返回執行結果。
實現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