談談柯里化方式的累加器實現

談談 JavaScript 中形如 add(1)(2)(3)(4) = 10 這種累加器方法的實現過程和思路前端

第一步:實現級聯

如果想要實現 fn()() 這種調用方式的函數,則在 fn 函數體內必定也會返回一個函數,以下:數組

function fn(){
    return function(){
        
    }
}

再進一步,如果想要實現 fn()()()... 不定次數的調用,則意味着每一層的返回值均爲一個函數,這就須要使用相似遞歸的方式去實現:app

function fn(){
    var _fn = function(){
        return _fn;
    }
    return _fn;
}

第二步:實現返回值

若是每一層的返回值均爲函數,那麼如何返回累加結果呢?也就是說,在函數調用的最後一層,這個返回值應該是一個值而非函數。這裏則須要使用 valueOf 這一方法,例子以下:框架

function fn1(){

}
console.log(fn1); // function ...

function fn2(){
    
}
fn2.valueOf = function(){
    return 1;
}
console.log(fn2); // function 1

注意,fn2 在控制檯的輸出結果爲 function 1,但其數值型隱式轉換的結果爲 1,也就是說 console.log(+fn2) 或是 var num = fn2 + 1 是能夠正常地做爲數值型進行計算的。而 fn2() 的方式仍然能夠將其做爲函數運算。函數

於是,咱們只要給 _fn 添加 valueOf 方法就能夠實如今不定次調用以後,獲得一個「值」而非「函數」,以下:測試

function fn(){
    var _fn = function(){
        return _fn;
    }
    _fn.valueOf = function(){
        return 12345;
    }
    return _fn;
}

這裏也可使用添加 toString() 的方式實現這一功能,與 valueOf() 作法一致。prototype

第三步:得到傳入的參數

這一步相對簡單,使用內置對象 arguments 便可實現對函數中所傳入的參數的獲取,以下:code

function foo(){
    console.log(arguments);
}

foo(1,2,3);

注意,這裏的 arguments 不是數組,而是對象:對象

{
    0 : 1,
    1 : 2,
    2 : 3,
    callee : function foo(),
    length : 3,
    Symbol(Symbol.iterator) : function values(),
    __proto__ : Object,
}

若要將其轉換爲數組,可使用以下方式:blog

var arr = [].slice.call(arguments);
// 或是
var arr = Array.prototype.slice.call(arguments);

若一個對象含有 length 屬性,則能夠經過這種方式轉換爲數組形式

第四步:實現參數保存

add(1)(2)(3)... 累加器是在最後一次調用後返回以前全部參數的累加和。也就是說咱們須要有一個地方能夠保存先前的值或是計算結果。

在以前的代碼框架下,顯然不能將其保存在內層的 _fn 中。由於每層調用都至關於又一次的 _fn() 執行,在其中定義的變量會被覆蓋。

使用全局變量固然是一種方式,可是這樣會污染全局空間,不是最佳方案。

考慮到對 fn()()()... 的調用實際返回的是內層的 _fn,意味着 fn 的局部變量其實也至關於 _fn 的全局變量。於是能夠將保存先前參數的職責交給 fn 中的一個變量,代碼以下:

function fn(){
    var numList = [];
    var _fn = function(){
    
        // 這裏測試思路是否可行
        numList.push(1);
        console.log(numList);
        
        return _fn;
    }
    _fn.valueOf = function(){
        return 12345;
    }
    return _fn;
}

console.log(fn()()()); // [1, 1]
// 注意這裏雖然調用三次,但實際只執行了兩次 push(1),第一次調用沒有執行內層的 _fn(),而只是返回了它。

結合第三步,咱們能夠經過 push() 或是 concat() 的方式將每一次的參數組合起來,以下:

function fn(){
    var numList = [].slice.call(arguments);
    var _fn = function(){
        // 注意這裏的 arguments 是傳入 _fn 的參數
        var innerArguments = [].slice.call(arguments);
        numList = numList.concat(innerArguments);
        console.log(numList);
        
        return _fn;
    }
    _fn.valueOf = function(){
        return 12345;
    }
    return _fn;
}

console.log(fn(1)(2)(3)); // [1, 2, 3]

固然,這裏也可使用 push() 的方式,將每一次的參數推入數組。

這一步還有另外一種思路:用每一次的求和代替參數數組的合併。

第五步:求和計算

既然已經獲得了所有的參數集合,對其進行求和就比較簡單了。最直接的方式固然是遍歷數組並累加獲得結果,也可使用數組的 reduce 方法實現,以下:

var arr = [1, 2, 3];
var sum = arr.reduce(function(num1, num2){
    return num1 + num2;
});
console.log(sum); // 6

結合第四步,替換 valueOf 中的返回值便可:

function fn(){
    var numList = [].slice.call(arguments);
    var _fn = function(){
        var innerArguments = [].slice.call(arguments);
        numList = numList.concat(innerArguments);
        
        return _fn;
    }
    _fn.valueOf = function(){
        return numList.reduce(function(num1, num2){
            return num1 + num2;
        });
    }
    return _fn;
}

console.log(fn(1)(2)(3));

將其進行簡化,獲得最終結果:

function fn(){
    var numList = [].slice.call(arguments);
    var _fn = function(){
        numList = numList.concat([].slice.call(arguments));
        return _fn;
    }
    _fn.valueOf = function(){
        return numList.reduce(function(i, j){return i+j;});
    }
    return _fn;
}

固然,採用這種實現方式,對於形如 fn(1, 2, 3)(4)(5, 6, 7) 的調用方式也是沒有問題的。


參考

  1. 前端基礎進階(八):深刻詳解函數的柯里化 - 簡書

  2. JS中的call()和apply()方法 - ITeye

相關文章
相關標籤/搜索