美文网首页让前端飞
函数柯里化小结

函数柯里化小结

作者: small_a | 来源:发表于2017-02-17 20:49 被阅读6374次

    最近学习了一下高阶函数,其中印象比较深刻的有函数消抖,函数节流以及函数柯里化(curry),在学习中也是从一头雾水到逐渐明了,所以写一篇文章对柯里化进行总结。

    什么是函数柯里化

    javascript忍者中说:在一个函数中首先填充几个参数(然后再返回一个新函数)的技术称为柯里化(Currying)。听起来跟bind的作用是一样的,其实bind也可以采用这种思想来实现(至于bind原本是怎么实现的,我不清楚,控制台输出Function.prototype.bind,输出是[native code],看不到,不清楚内部原理)。
    在很多文章中写到:柯里化通常也称部分求值,其含义是给函数分步传递参数,每次传递参数后部分应用参数,并返回一个更具体的函数接受剩下的参数,这中间可嵌套多层这样的接受部分参数函数,直至返回最后结果。
    举个不是很恰当的例子,有一个厨师,要做饭,但是餐馆的小儿没有把菜买齐,这样,小儿买一份原料,放在厨师厨房,再买一份,放在厨师厨房,等买齐了,叫厨师过来,好了,原料齐了,可以做饭了。这个时候,厨师利用原料,把饭做好。厨师就像一个函数,他有自己的功能(做饭),但是参数(原料)不齐,每次执行这个函数,在参数不齐的情况下,只能返回一个新的函数,这个新的函数已经内置了之前的参数,当参数齐了之后完成他本身的功能。

    问题

    要实现一个这样的加法函数,使得:

    add(1,2,3)(1)(2)(3)(4,5,6)(7,8)() === 42
    

    首先,可以看到,这个函数,只有当参数为空的时候,才执行之前所有数值的加法,这样的嵌套可以无限进行,当有参数的时候,add(1,2,3),这个时候的返回值应该是一个函数,这个函数存储了1,2,3但是没有执行加法(执行了也行,此处假设就不执行,只是起到保存参数的作用),这样,继续执行add(1,2,3)(2)()就能输出1+2+3+2=8
    要实现这样的一个函数,首先,返回值在参数不为空的时候必定返回一个函数,该函数还保存了之前的参数,这就需要用到闭包。
    最终的实现如下:

    // add 函数柯里化
    function add(){
        //建立args,利用闭包特性,不断保存arguments
        var args = [].slice.call(arguments);
           //方法一,新建_add函数实现柯里化
        var _add = function(){
            if(arguments.length === 0){
                //参数为空,对args执行加法
                return args.reduce(function(a,b){return a+b});
            }else {
                //否则,保存参数到args,返回一个函数
                [].push.apply(args,arguments);
                return _add;
            }
        }
        //返回_add函数
        return _add;
        
        // //方法二,使用arguments.callee实现柯里化
        // return function () {
      //       if (arguments.length === 0) {
      //           return args.reduce(function(a,b){return a+b});
      //       }
      //       Array.prototype.push.apply(args, arguments);
      //       return arguments.callee;
      //   }
    }
    console.log(add(1,2,3)(1)(2)(3)(4,5,6)(7,8)());//42
    

    实现的原理主要是:

    1. 闭包保存args变量,存储之前的参数
    2. 新建一个_add函数,参数的长度为0,就执行加法,否则,存储参数到args,然后返回函数自身(可以选择匿名函数,返回arguments.callee即可,意思相同,见代码中注释掉的部分,但是在严格模式下不能使用,所以还是使用方法一比较稳妥)。

    通用的函数来对普通函数进行柯里化

    可以看出来,柯里化其实是有特点的,需要一个闭包保存参数,一个函数来进行递归,这种模式是可以通过一个包装函数,对一些基本的函数进行包装之后的函数具有curry的特性。实现如下:

    //  通用的函数柯里化构造方法
    function curry(func){
        //新建args保存参数,注意,第一个参数应该是要柯里化的函数,所以args里面去掉第一个
        var args = [].slice.call(arguments,1);
        //新建_func函数作为返回值
        var _func =  function(){
            //参数长度为0,执行func函数,完成该函数的功能
            if(arguments.length === 0){
                return func.apply(this,args);
            }else {
                //否则,存储参数到闭包中,返回本函数
                [].push.apply(args,arguments);
                return _func;
            }
        }
        return _func;
    }
    
    function add(){
        return [].reduce.call(arguments,function(a,b){return a+b});
    }
    console.log(curry(add,1,2,3)(1)(2)(3,4,5,5)(5,6,6,7,8,8)(1)(1)(1)());//69
    

    上述代码定义了一个add函数,完成参数相加的功能,通过curry(add),使该函数能够进行柯里化,实现延迟执行。也可以采用arguments.callee来实现,原理相同,只是严格模式下不能使用而已。
    事实上,我在看函数节流以及函数消抖的大神写法的时候,其实应该把函数执行的上下文也保存下来,才能更加完善,有待后续更正。

    柯里化的作用

    看了很多文章,比较常见的功能有以下几种:

    1. 事件绑定的时候检测应该用dom几的方式,柯里化的话,只需要检测一次,返回一个新的函数来进行事件绑定(我觉得所有需要判断的地方,如果判断条件在一次访问中不改变的话,都可以写成柯里化的形式,从而达到只进行一次判断的效果)
    2. 利用柯里化的思想,可以自己写一个bind函数
    3. 延迟执行某个函数,(在一些文章中看到回调函数可以借助这个,还不是很理解,有待之后补充

    小结

    这篇文章主要是写了一点自己学习函数柯里化之后的总结,总体来说,学的时候很痛苦,学会了很简单。可能知识就是这么神奇,会者不难。

    参考文章

    从一道面试题谈谈函数柯里化(Currying)

    简单理解JavaScript中的柯里化和反柯里化

    1. 浅析 JavaScript 中的 函数 currying 柯里化

    相关文章

      网友评论

        本文标题:函数柯里化小结

        本文链接:https://www.haomeiwen.com/subject/fkwlwttx.html