柯里化
高阶函数
在说明柯里化之前,首先需要理解高阶函数的定义
高阶函数是指以函数作为参数的函数,伪代码可以理解为
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