函數柯里化(curry)能夠簡單的理解是將具備多個參數的函數,轉換成具備較少的參數的函數的過程,具體將具備多個參數的函數轉換成一系列嵌套函數,使用一部分參數調用柯里化函數返回一個新函數,每次返回的函數處理剩餘的參數。javascript
咱們經過一個經常使用的函數舉例說明柯里化函數的使用。html
function multiply(a,b,c){
return a * b * c;
}
multiply(2,2,5)// 20
複製代碼
這個簡單的函數定義了三個參數,參數相乘返回運算結果。java
咱們將上面的函數重構成柯里化結構的函數,用以說明柯里化的使用方法。git
function multiply(a,b,c){
return a=> {
return b=> {
return c => {
return a * b * c;
}
}
}
}
multiply(2)(2)(5) // 20
複製代碼
經過柯里化函數,咱們將一個功能改形成一系列的功能,將以前須要一次傳遞的多個參數,分爲三次傳遞,每次傳遞的函數做爲下一個函數的內鏈調用。編程
爲了便於理解函數柯里化的調用,咱們能夠將上面的柯里化函數multiply(2)(2)(5) 調用過程分開書寫。segmentfault
const multiply_fir = multiply(2);
const multiply_sec = multiply_fir(2);
const result = multiply_sec(5);
console.log(result ); // 20
複製代碼
理解了返回函數的內鏈調用的過程,如今咱們開始實現柯里化函數的內部原理:數組
// 基於ES6原理實現
let curryFun = (fun, length, ...args) => {
let len = args.length;
return len === length ? fun(...args) : curryFun.bind(null, fun, len, ...args);
}
// 測試
let addFun = curryFun(function(){
let sum = 0;
let args = [].slice.call(arguments, 0);
let len = args.length;
for(let i=0;i<len;i++) {
sum += args[i];
}
return sum;
})
let sun = addFun(2,12,19)(198)(100)();
console.log(sun); //331
複製代碼
柯里化函數是函數式編程的一種很實用的實踐。函數式編程是一種重要的編程範式,最重要的特色是函數做爲第一等公民,而且強調函數自己的純粹性,對於相同的輸入參數返回相同的結果沒有其餘反作用。ide
因此咱們建立的柯里化函數自己是一個純函數,而且具備本身的特徵,能夠建立有意義的代碼模塊。函數式編程
當咱們的函數參數大部分狀況下相同的時候,咱們能夠利用柯里化解決公共參數複用的問題。函數
const obj = { name: 'test' };
const foo = function (prefix, suffix) {
console.log(prefix + this.name + suffix);
}.bind(obj, 'currying-');
foo('-function'); // currying-test-function
複製代碼
bind方法將第一個參數設置成函數執行上下文,其餘的參數傳遞給調用方法。
若是一個值要通過多個函數,才能變成另一個值,就能夠把全部中間步驟合併成一個函數,這叫作"函數的合成"(compose)
// ES5寫法
const compose = function (f, g) {
return function (x) {
return f(g(x));
};
}
複製代碼
經過封裝一系列處理步驟,能夠減小對中間變量的操做,也是一種代碼複用。好比咱們須要操做一個數組,返回的對象是原數組每一個元素上+1後的新的數組,這個需求很簡單:
const list = [0, 1, 2, 3];
const list1 = list.map(elem => elem + 1); // => [1, 2, 3, 4]
複製代碼
當咱們需求發生變化,須要增長+2的操做結果:
const list = [0, 1, 2, 3];
const list1 = list.map(elem => elem + 1); // => [1, 2, 3, 4]
const list2 = list.map(elem => elem + 2); // => [2, 3, 4, 5]
複製代碼
咱們在維護這樣一系列的代碼的時候,很容易想到將list.map(elem => elem + 2)這樣一個過程,進行封裝,咱們只須要傳遞咱們須要操做的參數便可,這樣能夠提升代碼的可讀性。
咱們的代碼結構以下:
const plus1 = plus(1);
const plus2 = plus(2);
const list = [0, 1, 2, 3];
const list1 = list.map(plus1); // => [1, 2, 3, 4]
const list2 = list.map(plus2); // => [2, 3, 4, 5]
複製代碼
咱們只須要實現plus()函數便可完成代碼的封裝,提升代碼的維護性和可讀性,JS柯里化能夠實現咱們的需求。
function plus(a) {
return function(b) {
return a + b;
}
}
複製代碼
首先咱們須要瞭解JavaScript函數的執行機制的一些基礎內容:JS是經過執行上下文堆棧管理函數的執行和變量的做用域。JS的函數調用實際上是在函數執行上下文堆棧上建立新的記錄(幀)。
每一個執行上下文幀都須要必定的內存空間,空幀大約須要48字節,每一個參數和局部變量通常須要8字節。
函數柯里化是將函數一次執行的過程,變成嵌套函數執行屢次。根據JS的執行機制咱們能夠可以理解函數柯里化,使用到了更多的嵌套函數,致使更多的執行上下文堆棧開銷。
JS是單線程的,有且只有一個全局上下文,全局上下文共享,函數在調用的時候首先會在暫停全局上下文,建立新的上下文堆棧,使得新上下文處於激活狀態。經過scopeChain建立到父類上下文的連接。激活上下文執行完畢會被彈出,父類上下文從新處於激活狀態。
有了這些基礎知識咱們能夠利用JS執行機制,解釋 multiply(a, b, c)柯里化先後上下文堆棧的建立過程:
當函數調用建立的上下文處於激活狀態是的上下文堆棧狀況:
堆棧層級 | 上下文 | 狀態 |
---|---|---|
2 (棧頂) | multiply | 激活 |
1 | 全局上下文 | 被暫停 |
同上的分析過程,咱們能夠判斷multiply柯里化函數在multiply_sec(5)運行時的堆棧狀況。
堆棧層級 | 上下文 | 狀態 |
---|---|---|
4(棧頂) | multiply_sec | 激活 |
3 | multiply_fir | 被暫停 |
2 | multiply | 被暫停 |
1 | 全局上下文 | 被暫停 |
經過對上下文的建立過程的分析,咱們能夠理解嵌套深的函數會建立更多的上下文幀,不合理的柯里話函數會致使更多的內存消耗,咱們在使用柯里化的時候須要瞭解清楚函數執行上下文的執行原理,合理的規劃實現方式。
咱們也能夠從新定義JS的柯里化:柯里化是一種詞法做用域,返回的函數是一個接受後續參數的包裝器。