JavaScript:函数柯里化

时间:2021-07-26 16:50:54   收藏:0   阅读:0

什么是js柯里化(curry)?

在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
举例来说,一个接收3个参数的普通函数,在进行柯里化后,柯里化版本的函数接收一个参数并返回接收下一个参数的函数,该函数返回一个接收第三个参数的函数。最后一个函数在接收第三个参数后,
将之前接收到的三个参数应用于原普通函数中,并返回最终结果。
来看这个例子:

//普通函数
function fn(a,b,c,d,e) {
  console.log(a,b,c,d,e)
}
//生成的柯里化函数
let _fn = curry(fn);

_fn(1,2,3,4,5);     // print: 1,2,3,4,5
_fn(1)(2)(3,4,5);   // print: 1,2,3,4,5
_fn(1,2)(3,4)(5);   // print: 1,2,3,4,5
_fn(1)(2)(3)(4)(5); // print: 1,2,3,4,5

对于已经柯里化后的 _fn 函数来说,当接收的参数数量与原函数的形参数量相同时,执行原函数;
当接收的参数数量小于原函数的形参数量时,返回一个函数用于接收剩余的参数,直至接收的参数数量与形参数量一致,执行原函数。
当我们知道柯里化是什么了的时候,我们来看看柯里化到底有什么用?

柯里化的用途

柯里化实际是把简单的问题复杂化了,但是复杂化的同时,我们在使用函数时拥有了更加多的自由度。
而这里对于函数参数的自由处理,正是柯里化的核心所在。
柯里化本质上是降低通用性,提高适用性。来看一个例子:
我们工作中会遇到各种需要通过正则检验的需求,比如校验电话号码、校验邮箱、校验身份证号、校验密码等,
这时我们会封装一个通用函数 checkByRegExp ,接收两个参数,校验的正则对象和待校验的字符串

function checkByRegExp(regExp,string) {
    return regExp.test(string);  
}

checkByRegExp(/^1\d{10}$/, ‘18642838455‘); // 校验电话号码
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, ‘test@163.com‘); // 校验邮箱

上面这段代码,乍一看没什么问题,可以满足我们所有通过正则检验的需求。 但是我们考虑这样一个问题,如果我们需要校验多个电话号码或者校验多个邮箱呢?

我们可能会这样做:

checkByRegExp(/^1\d{10}$/, ‘18642838455‘); // 校验电话号码
checkByRegExp(/^1\d{10}$/, ‘13109840560‘); // 校验电话号码
checkByRegExp(/^1\d{10}$/, ‘13204061212‘); // 校验电话号码

checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, ‘test@163.com‘); // 校验邮箱
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, ‘test@qq.com‘); // 校验邮箱
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, ‘test@gmail.com‘); // 校验邮箱

我们每次进行校验的时候都需要输入一串正则,再校验同一类型的数据时,相同的正则我们需要写多次,
这就导致我们在使用的时候效率低下,并且由于 checkByRegExp 函数本身是一个工具函数并没有任何意义,
一段时间后我们重新来看这些代码时,如果没有注释,我们必须通过检查正则的内容,
我们才能知道我们校验的是电话号码还是邮箱,还是别的什么。
此时,我们可以借助柯里化对 checkByRegExp 函数进行封装,以简化代码书写,提高代码可读性。

//进行柯里化
let _check = curry(checkByRegExp);
//生成工具函数,验证电话号码
let checkCellPhone = _check(/^1\d{10}$/);
//生成工具函数,验证邮箱
let checkEmail = _check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);

checkCellPhone(‘18642838455‘); // 校验电话号码
checkCellPhone(‘13109840560‘); // 校验电话号码
checkCellPhone(‘13204061212‘); // 校验电话号码

checkEmail(‘test@163.com‘); // 校验邮箱
checkEmail(‘test@qq.com‘); // 校验邮箱
checkEmail(‘test@gmail.com‘); // 校验邮箱

再来看看通过柯里化封装后,我们的代码是不是变得又简洁又直观了呢。
经过柯里化后,我们生成了两个函数 checkCellPhone 和 checkEmail,
checkCellPhone 函数只能验证传入的字符串是否是电话号码,
checkEmail 函数只能验证传入的字符串是否是邮箱,
它们与 原函数 checkByRegExp 相比,从功能上通用性降低了,但适用性提升了。
柯里化的这种用途可以被理解为:参数复用

如何封装柯里化工具函数

接下来,我们来思考如何实现 curry 函数。
回想之前我们对于柯里化的定义,接收一部分参数,返回一个函数接收剩余参数,接收足够参数后,执行原函数。
我们已经知道了,当柯里化函数接收到足够参数后,就会执行原函数,那么我们如何去确定何时达到足够的参数呢?
我们有两种思路:

通过函数的 length 属性,获取函数的形参个数,形参的个数就是所需的参数个数
在调用柯里化工具函数时,手动指定所需的参数个数

我们将这两点结合以下,实现一个简单 curry 函数:

 var foo = function(a,b,c){
            console.log(a,b,c)
        }

        var curry= function(fn,args){
            var args = args || []
            var len = fn.length
            return function(){
                var _args = args.concat(Array.prototype.slice.call(arguments,0))
                if(_args.length>=len){
                    fn.apply(this,_args)
                }else{
                    return curry.call(this,fn,_args)
                }
            }
        }
        var f = curry(foo)
        f(1,2,3) // 123
        f(2,4)(5) // 245
        f(1)(2)(3) // 123

还有很多有趣的内容,比如占位符。

参考文章

1.https://juejin.cn/post/6844903882208837645#comment
2.https://github.com/mqyqingfeng/Blog/issues/42

评论(0
© 2014 mamicode.com 版权所有 京ICP备13008772号-2  联系我们:gaon5@hotmail.com
迷上了代码!