javascript之函数柯里化及偏函数

  第一次看到函数柯里化这个词仍是在"js高级程序设计"这本书上看到的,从一开始的一脸懵逼到如今的慢慢理解,这也是一个学习的过程吧。同时也慢慢明白了何为高屋建瓴,学习是一个积累的过程,当你积累的够多时,学习其余的新东西也就事半功倍了。javascript

一、函数柯里化

  柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,而且返回接受余下的参数且返回结果的新函数的技术。
  本质是下降通用性,提升适用性,实际上是对闭包的极致应用。用闭包把参数保存起来,当参数的数量足够执行函数了,就开始执行函数。基本方法就是使用一个闭包返回一个函数前端

//函数柯里化的基本实现
    function curry(fn){
        let args=[].slice.call(arguments,1);
        return function(){
            return fn.apply(this,[..args,...arguments]);
        }
    }
    function foo() {
       console.log(...arguments);
    }
    //调用方法1
    curry(foo, 9, 3, 4)(34, 45);//9,3,4,34,45
    //调用方法2
    curry(foo)( 9, 3, 4,34, 45);//9,3,4,34,45
    //调用方法3
    curry(foo,9,3,4,34,45)
    
    //进阶版1,当传入的参数个数知足fn函数的参数个数时,开始执行fn函数
   function curry(fn,args=[]){
        let length=fn.length;//表示fn函数中非默认参数的长度
        return function(){
            //内外函数传入的参数数组
            let argArr=[...args,...arguments];
            if(argArr.length<length){
                //参数不够时则递归
                return curry.apply(this,fn,argArr);
            }else{
                //参数够了就直接执行
                return fn.apply(this,argArr)
            }
        }
    }
    function foo(a,b,c,d){
         console.log([a, b, c,d]);
    }
    var fn = curry(foo);
    fn("a", "b", "c") // ["a", "b", "c"]
    fn("a", "b")("c") // ["a", "b", "c"]
    fn("a")("b")("c") // ["a", "b", "c"]
    fn("a")("b", "c") // ["a", "b", "c"]
    
    //进阶版2,使用占位符
    //holes用于存放占位符的在args中的位置
    function curry3(fn,args=[],holes=[]){
        let length=fn.length;
        return function(){
            let _args=args.slice(0);
            let _holes=holes.slice(0);
            let argsLen=_args.length;
            let holesLen=_holes.length
            let index=0;//表示当前args中的占位符个数
            //判断参数个数是否大于0
            if(argsLen>0){
                for(let i=0;i<arguments.length;i++){
                    let arg=arguments[i];
                    //判断是否为占位符
                    if(arg==="_"){
                        index++;
                        if(index>holesLen){
                            //上一次的占位符已经彻底被这次的占位符替换了,并添加这次占位符到占位符数组中以及总参数数组中
                            _holes.push(_args.length);
                            _args.push(arg);
                        }                   
                    }
                    // //表示不是占位符,并判断这次占位符个数与上次占位符个数,小于则替换占位符,大于则添加args后面
                    else if(index<holesLen){
                        _args.splice(holes[index],1,arg);
                         //将占位符索引清除
                         _holes.splice(index,1)
                    }else{//表示不是占位符,可是args中没有占位符
                        _args.push(arg)
                    }
                }
            }else{
                //表示首次传参
                for(let i=0;i<arguments.length;i++){
                    let arg=arguments[i];
                    if(arg==="_"){
                        _holes.push(i);
                    }
                    _args.push(arg);
                }
            }
            //参数个数还不够
            if(_args.length<length){
                return curry3.call(this,fn,_args,_holes)
            }//非占位符参数不够
            else if(_args.length-_holes.length<length){
                return curry3.call(this,fn,_args,_holes)   
            }//占位符的索引不能小于知足条件的参数的个数
            else if(_holes[0]<length){
                return curry3.call(this,fn,_args,_holes);   
            }
            else{
                return fn.apply(this,_args)
            }
        }
    }
    我的以为占位符的柯里化函数可以掌握就掌握吧!!!
    测试:
    fn(1, 2, 3, 4, 5);
    fn('_', 2, 3, 4, 5)(1);
    fn(1, '_', 3, 4, 5)(2);
    fn(1, '_','_','_', 3)('_','_', 4)(2)(5);
    fn(1, '_', '_','_', '_','_', '_', 4)('_', 3)(2)(5);
    fn('_','_', 2)('_', '_','_', 4)(1)(3)(5)
复制代码

测试结果以下: java

二、偏函数

  柯里化是将一个多参数函数转换成多个单参数函数,也就是将一个 n 元函数转换成 n 个一元函数。git

  偏函数(局部应用)则是固定一个函数的一个或者多个参数,也就是将一个 n 元函数转换成一个 n - x 元函数。本质上能够将偏函数当作是柯里化的一种特殊状况。bind函数的实现过程就是偏函数。
  它两的区别在于偏函数会固定你传入的几个参数,再一次性接受剩下的参数,而函数柯里化会根据你传入的参数不停的返回函数,直到参数个数知足被柯里化前函数的参数个数github

//偏函数的基本实现,能够看出跟柯里化的基本实现是同样的
    function partial(fn){
        let args=[].slice.call(arguments,1);
        return function(){
            return fn.apply(this,[...args,...arguments]);
        }
    }
    //进阶版 占位符
    function partial(fn){
        let args=[].slice.call(arguments,1);
        return function(){
            let index=0;//用于统计占位符的个数
            let len=args.length;
            for(let i=0;i<len;i++){
                args[i]=args[i]==="_"?arguments[index++]:args[i];
            }
            for(;index<arguments.length;index++){
                args.push(arguments[index]);
            }
            return fn.apply(this,args);
        }
    }
    测试:
    function foo(a,b){
        console.log(a+b);
    }
    let f=partial(foo,2);
    f(3);//5
    function foo(a,b){
        console.log(a+b);
    }
    let f=partial(foo,2,"_");
    f(3);//5
复制代码

参考连接:
一、javascript高级程序设计之函数柯里化(P604)。
二、柯里化
三、JavaScript专题之函数柯里化
四、一个合格的中级前端工程师必需要掌握的 28 个 JavaScript 技巧数组

相关文章
相关标签/搜索