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 技巧數組

相關文章
相關標籤/搜索