最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 浅析函数柯里化

    正文概述 掘金(程序员思语)   2021-06-27   576

    这是我参与更文挑战的第24天,活动详情查看: 更文挑战

    前言

    维基百科:在计算机科学中,柯里化(Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

    科里化面试题

    在前端面试中有一个关于柯里化的面试题,流传甚广。

    // 实现一个add方法,使计算结果能够满足如下预期:
    add(1)(2)(3) = 6
    add(1, 2, 3)(4) = 10
    add(1)(2)(3)(4)(5) = 15
    

    很明显,计算结果正是所有参数的和,add方法每运行一次,肯定返回了一个同样的函数,继续计算剩下的参数。

    1. 如果只调用2次

    function add(a) {
        return function(b) {
            return a + b;
        }
    }
    
    console.log(add(1)(2));  // 3
    

    2. 如果只调用3次

    function add(a) {
        return function(b) {
            return function (c) {
                return a + b + c;
            }
        }
    }
    
    console.log(add(1)(2)(3)); // 6
    

    3. 如果调用的次数不确定?

    其实上面的做法都是利用闭包的特性,将所有的参数,集中到最后返回的函数里进行计算并返回结果。因此我们在封装时,主要的目的,就是将参数集中起来计算。可以这样写个小结:

    function add() {
        // 第一次执行时,定义一个数组专门用来存储所有的参数
        var _args = [].slice.call(arguments);
    
        // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
        var adder = function () {
            var _adder = function() {
                [].push.apply(_args, [].slice.call(arguments));
                return _adder;
            };
    
            // 利用隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
            _adder.toString = function () {
                return _args.reduce(function (a, b) {
                    return a + b;
                });
            }
    
            return _adder;
        }
        return adder.apply(null, [].slice.call(arguments));
    }
    
    // 输出结果,可自由组合的参数
    console.log(add(1, 2, 3, 4, 5));  // 15
    console.log(add(1, 2, 3, 4)(5));  // 15
    console.log(add(1)(2)(3)(4)(5));  // 15
    

    利用闭包的特性通过一些方法将所有的参数收集在一个数组里,并在最终隐式转换时将数组里的所有项加起来。因此我们在调用add方法的时候,参数就显得非常灵活。

    函数科里化

    函数柯里化就是创建已经设置单个参数或者多个参数的函数,函数变为接受一个参数,返回一个值,柯里化的用途主要是参数复用。再举一个 demo :

    // 参数复用
    function add(a, b) {
        return a + b;
    }
    add(1,2)  //3
    
    // 柯里化之后可以这样
    var addCurry = curry(add);
    addCurry(1)(2); //3
    

    或许针对这种简单的将两个数相加的场景,柯里化显得有点多余。但是如果我们想使用这个函数完成通用的事情,比如为所有的数加5,就可以使用addCurry(5)(x),使得将两个数相加的函数有了通用性。

    通用版

    var curry = function(func){
    
        var args = [].slice.call(arguments,1);
    
        return function(){
    
            var newArgs = args.concat([].slice.call(arguments));
    
            return func.apply(this,newArgs);
    
        }
    
    }
    

    首先将参数进行分割,也就是将除了func之外的参数存进args。返回的函数接受新传入的参数并与之前的参数合并,从而将所有的参数传入函数中,并执行真正的函数。

    改进版

    比如说add这个函数接受两个参数,那么针对柯里化之后的函数,若传入的参数没有到达两个的话,就继续调用curry,继续接受参数。若参数到达2个了,就直接调用add函数。

    var curry = function(func,args){
        var length = func.length;
        args = args||[];
    
        return function(){
            newArgs = args.concat([].slice.call(arguments));
            if (newArgs.length < length) {
                return curry.call(this,func,newArgs);
            } else {
                return func.apply(this,newArgs);
            }
        }
    }
    
    var addCurry = curry(add);
    addCurry(1,2) //3
    addCurry(1)(2) //3
    

    进阶版

    但这一版柯里化函数仍然不能完全满足要求,因为它只针对有特定参数个数的函数适用。再回到 前面的那道面试题,题目要求不变。

    add(1)(2)(3) = 6
    add(1, 2, 3)(4) = 10
    add(1)(2)(3)(4)(5) = 15
    

    前面写的已经不能满足需求,这里我们改用函数的 toString 来完成。

    当我们返回函数的时候,会调用函数的toString来完成隐式转换,这样输出的就不是函数的字符串形式而是我们定义的toString返回的值。这样就既可以保持返回一个函数,又能够得到一个特定的值。

    function add(){
        // 第一次执行时,定义一个数组专门用来储存所有的参数
        var args = [].slice.call(arguments);
        // 在内部声明一个函数,
        // 利用闭包的特性保存_args并收集所有的参数值
        var fn = function(){
            var newArgs = args.concat([].slice.call(arguments));
            return add.apply(null,newArgs);
        } 
        // 利用隐式转换的特性,
        // 当最后执行时隐式转换,并计算最终的值返回
        fn.toString = function(){
            return args.reduce(function(a, b) {
                return a + b;
            })
        }
        return fn ;
    }
    
    // 可以接受任意个数的参数
    add(1)(2,3) //6
    add(1)(2)(3)(4)(5) //15
    

    真正的函数柯里化

    无论是上面的面试题还是demo,我们从中不难看出,柯里化是将接受多个参数转换为接受一个单一参数。简单梳理一下:

    假设你有一个储钱罐 countMoney 函数,和一个记录本 arr 数组,当你每月有空钱时进行储存,每次在 arr 中记录一次,存入储钱罐中:

    var arr=[];
    var countMoney=function(arr){
        var sum=0;
        for(var i=0;i<arr.length;i++){
            sum+=arr[i];
        }
        return sum;
    }
    
    arr.push(1);
    arr.push(2);
    
    countMoney(arr);
    

    可以通过这种方式来进行存储,但是有本记录,是会被发现的,所以这个时候可以这样:

    // 每次存储是调用一次,不需要再次记录下来
    countMoney(1);
    countMoney(2);
    
    // 等到真正需要的时候我们可以直接计算出来这个总值
    countMoney(); //3
    

    于是问题解决的方式变为柯里化问题,需要将多个参数接受转换为接受单一参数的问题。于是我们可以使用下面的方式进行处理。

    var countMoney = (function() {
        let moneys = 0;
        let arr = [];
    
        var result = function() {
            // 判断是否还有参数,如果没有,则返回存储起来值的总和
            if(arguments.length == 0) {
                for(var i = 0; i < arr.length; i++) {
                    money += arr[i];
                }
                return money;
            } else {
                // arguments 是个类数组来着,应该用展开符展开才能push进去
                // 通过arguments 处理可以传入多个参数值
                console.log(...arguments)
                arr.push(...arguments);
                return result;
            }
        }
        return result;
    })();
    
    countMoney(1)(2)(3)
    countMoney(6)
    

    上面的例子完全可以实现柯里化,并且进行扩展,现在可以安全的存放钱了。

    总结

    柯里化(Currying),又称为部分求值,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回一个新的函数的技术,新函数接受余下参数并返回运算结果。

    接收单一参数,因为要携带不少信息,因此常常以回调函数的理由来解决。将部分参数通过回调函数等方式传入函数中返回一个新函数,用于处理所有的想要传入的参数。

    实际上,在JavaScript的很多思想和设计模式中,闭包是个很常见的且很重要的东西,上面两个例子代码中,本质上就是利用了闭包。上面的 countMoney 函数是个立即执行的函数,返回一个新函数,而这个新函数实际上就是一个闭包,这个新函数把每次接收到的参数都存储起来,并且继续返回一个新函数,当发现某次调用时没有传入参数,那就意味着要进行数据统计,从而把之前存储的数据一次拿出来计算,最后返回计算结果。


    起源地下载网 » 浅析函数柯里化

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元