上一节我们认识了高阶函数Javascript--高阶函数之从实践中感悟(封装new,forEach,map),知道了高阶函数是接收一个或多个函数作为参数的,并且其功能适用于增强函数的原有功能!
本节我们要来讲讲高阶函数的两个应用,柯里化和组合,这两个技术使得我们的函数能够具有更强大的功能,更高的自由度;
柯里化(Currying)
柯里化是一种将一个带有多个参数的函数 转化为一系列带有部分参数的函数的过程。
例一:校验函数柯里化
假设有一个函数,可以用来接收一个正则表达式和一个被校验值:
js
function check(reg, checkValue) {
return reg.test(checkValue);
}
但是我们的项目中需要校验的有手机号,邮箱;我们不希望每次我们进行校验的时候都传递一个reg
正则,那么我们可以将校验函数具体化,封装为两个函数:
js
function checkPhone(phoneNumber) {
return check(/^1[34578]\d{9}$/, phoneNumber);
}
function checkEmail(email) {
return check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, email)
}
假如我们之后项目又需要校验密码,那么我们需要再重写一个函数:checkPassword
;
而如果我们使用柯里化则会简化这一过程; 我们期望这样封装:
js
function _check = currying(check);
export function checkPhone=_check(/^1[34578]\d{9}$/);
前面我们说了,柯里化就是将一个多参的函数,转化为一系列单一参数的函数,在这里就是将check
函数柯里化,然后提前将校验规则传入并返回一个新的函数,这个函数可以专门用来校验手机,接收校验值即可;
那么如何封装这样一个currying
函数呢?
js
function currying(func, ...args) {
const wrapper = function (...thenArgs) {
//接收到足够的参数后执行
if (args.length + thenArgs.length >= func.length) {
console.log('参数满足,开始执行');
return func.apply(func, [...args, ...thenArgs]);
} else {
//参数不完整,那么就将后续的参数先存起来
console.log('参数不足,接收参数为:' + [...thenArgs] + " 所有参数为:" + [...args, ...thenArgs])
return currying(func, ...args, ...thenArgs);
}
}
return wrapper;
}
接着我们就可以这样封装我们的check
了:
js
//check函数,接收两个参数
function check(reg, checkValue) {
return reg.test(checkValue);
}
//将check函数柯里化
const curryingCheck = currying(check);
//校验手机号,先向check函数传递一个校验规则
const checkPhone = curryingCheck(/^1[34578]\d{9}$/);
//使用
checkPhone(123);
你会发现:柯里化就是让一个函数具有收集并保存参数的功能,这就使得我们的封装变得更加灵活!我觉得柯里化这个技术应该会在第三方库的开发上大放异彩!
例二:格式化函数柯里化
基于例一的柯里化知识,我想这个案例更加实用:柯里化map实现一个格式化函数;
在开发中,我们需要为一个数组的每一项添加单位(%,元,米...),怎么做呢?希望大家能先自己动手实现一下!
首先我们知道柯里化是让一个函数具有收集并保存参数的功能 ,也就是我们要柯里化的函数需要接收目标数组以及一个表示单位的参数unit
:
js
function format(array, unit) {
return array.map((item) => {
return item + unit;
})
}
然后我们将format
柯里化:
js
//柯里化
const formatCurrying = currying(format);
//传入单位构造出对应单位的format函数
const persentFormat = formatCurrying("%");
const metreFormat = formatCurrying("米");
//实用
console.log(persentFormat([1, 2, 10]));
console.log(metreFormat([1, 2, 10]))
小结
- 柯里化是一种将一个带有多个参数的函数 转化为一系列带有部分参数的函数的过程。
- 柯里化是让一个函数具有收集并保存参数的功能,高阶函数的思想增强了函数
- 柯里化使得函数可以从这样调用:
func(a,b,c)
变成这样调用:func(a)(b)(c)/func(a,b)(c)
;使得封装的方法更灵活
组合(Compose)
函数组合是一种将多个函数结合起来创建新函数 的技术。组合的核心思想是将一个函数的输出作为另一个函数的输入,通过这种方式连接多个函数,形成一个新的函数。
在lodash.js
中,有这样一个方法flow
:
我们来一起看看他的源码:
ts
function flow(...funcs: Function[]) {
//传入函数的个数
const length = funcs.length;
let i = length;
//判断参数合法性
while (i--) {
if (typeof funcs[i] !== 'function') {
throw new TypeError('Expected a function');
}
}
//返回的函数
return function (this: any, ...args: any[]) {
let j = 0;
//执行第一个函数,保留其结果
let result = length ? funcs[j].apply(this, args) : args[0];
//循环调用,将result作为下一个函数的参数
while (++j < length) {
result = funcs[j].call(this, result);
}
return result;
};
}
可以看出,组合也是通过高阶函数的思维,将多个函数的功能进行组合,只不过对于组合函数之间的参数有约定;
例一:设备&权限校验
我们想要判断一个用户他的访问设备以及访问权限,利用组合的方式来完成这个功能;
我们期望这样使用:
js
const checkUser = compose(checkEnv,checkAuth);
我们约定checkEnv的返回值是访问的设备信息,而checkAuth接收到这个设备信息后仅保存并最终与权限一同返回;
则可以写出如下代码:
js
function checkEnv() {
const env = {
isMobile: false,
isAndroid: false,
isIOS: false,
};
const ua = navigator.userAgent;
env.isMobile = 'ontouchstart' in document;
env.isAndroid = !!ua.match(/android/);
env.isIOS = !!ua.match(/iphone/);
return env;
}
function checkAuth(env) {
const random = Math.random() * 10;
if (random < 5) {
return {
env,
isAdmin: false,
};
} else {
return {
env,
isAdmin: true,
};
}
}
紧接着我们只需要将其组合一下就可以了:const checkUser = flow(checkEnv,checkAuth)
;
此外,你有没有觉得将上一个函数的返回值作为下一个函数的参数与reduce
十分契合,不妨改造一下flow
方法:
js
function compose(...funcs) {
return function () {
return funcs.reduce((preReturn, curFunc) => {
return curFunc(preReturn);
}, funcs[0]?.())
}
}
小结
- 组合就是将多个函数功能合并并返回一个新的函数,该函数具有多个函数的功能,这也是高阶函数的增强功能
- 组合需要约定好函数之间的参数与返回值
- 组合一般是将这样调用:
funcA(funcB(funcC))
转变为:compose(funcC,funcB,funcA)
- 组合在redux中有应用,同时也很像中间件:洋葱模型
- 你甚至可以将组合与柯里化结合,那么就可以这样使用了:
compose(funcC,funcB)(funcA)