前端之函式柯里化Currying

晴天633發表於2018-12-27

什麼是柯里化

在電腦科學中,柯里化(Currying)是一種技術(技巧),能夠把本來接受 n 個引數的函式A,轉換成只接收一個引數的函式B(B中的唯一引數,就是A的多個引數中的 第一個 引數)。

然後新函式B返回的,還是一個函式,記為C(注意原A中返回的不一定是啥)。這個C函式又只能接收一個引數(即為A函式的 第二個 引數)......依次不斷往復,直到返回一個接收A的第 n 個引數的函式F,F中判斷這是最後一個函式了,執行,然後返回最終的值。

很繞口,看個例子:

// 我們想要實現一個把引數相加,返回和的函式
function adder(a, b) {
	return a + b;
}
adder(4,6);
// 結果為:10
複製程式碼

當然也可以這樣寫(非柯里化)

function adder() {
	var sum = 0;
	for(var i=0;i<arguments.length;i++)
		sum += arguments[i];
	return sum;
}
adder(4,6);
// 10
複製程式碼

用柯里化實現

var adder = function(num) {
    var n = num;   // 對應的為引數4
    return function(y) {
        return n + y; // y為6   返回 4+6
    }
}
adder(4)(6)    // 10
複製程式碼

從上面可以看到,本來adder是傳2個引數adder(4, 6),柯里化的方式後,就成為了adder(4)(6),即每次接受一個引數並放回一個函式,然後鏈式的執行。。。

可以看到,adder函式中有著內部函式,內部函式一直引用著n,這就形成了一個閉包,所以柯里化是閉包的應用之一,面試時直接可以答~~~

進階

從上面可以大概瞭解了,柯里化就是,

把原來的函式 adder(3,5,6,7,8,9) ==> adder(3)(5)(6)(7)(8)(9)()

注意:最後一次呼叫沒有引數(),所以adder中可以通過判斷是否有引數,去判斷我是要繼續相加,還是返回求和值了

或是這樣也可以

adder(3)
adder(5,6,7)
adder(8,9)
adder() // 無引數時返回和
複製程式碼

那麼這麼做的好處是什麼呢?

  • 延遲計算,何時想要結果了,直接adder()就行了
  • 通常也成為部分求值,給函式分步傳遞引數,逐步縮小函式的適用範圍,逐步求解的過程。

所以我們可以在寫一個函式的時候,用柯里化的思想去寫;但是我們也可以對任何一個原有的函式,將他柯里化了,變成柯里化的思想與形式,看思路~

例:

一個原有的函式

// 不知道這test函式是啥功能,不用管他是幹啥的,但很顯然不是簡單的求和,
// 所以你不能繼續用 迴圈 arguments 了。。。
function test(name, id, num, score, height) {
    
}
複製程式碼

將他柯里化,就是要將他

test("chen", 45, 789, 284.2, 178) ==> test2("chen")(45)(789)(284.2)(178)()

其中test2就是經過柯里化包裝後的test

包裝的思想是

  1. test2中建個陣列arr,多次呼叫後,arr就成了 ["chen", 45, 789, 284.2, 178]
  2. 當沒引數時,執行test.apply(arr) ,,, 想起apply幹啥的不
  3. 所以,其實就是把一堆引數給存起來先,然後最後再執行一次執行test

通用封裝程式碼如下,一定要搞懂!

前端之函式柯里化Currying

實際應用

如果還是沒覺得有啥用,看兩個實際的例子

1. 瀏覽器事件

還記得事件要區分IE和非IE不了

var addEvent = function(el, type, fn, capture) {
    if(window.addEventListener) {
        el.addEventListener(type, function(e) {
            fn.call(el, e)
        }, capture)
    } else {
        el.attachEvent('on'+type, function(e) {
            fn.call(el, e)
        })
    }
}
複製程式碼

也就是說,我們會呼叫addEvent,但是每次的話都得執行內部的if...else....

所以可以用柯里化,改成

var curEvent = (function() {
    if(window.addEventListener) {
        return function(el, sType, fn, capture) { // return funtion
            el.addEventListener(sType, function() {
                fn.call(el, e)
            }, capture)
        }
    } else {
        return function(el, sType, fn) {
            el.attachEvent('on'+sType, function(e) {
                fn.call(el, e)
            })
        }
    }
})

var addEvent = curEvent();  // addEvent 這回得到的,就是if..else...裡面的那個returnfunction,所以只需要curEvent()執行一遍判斷了if..else,其他時候就都不需要判斷了

addEvent(elem)
複製程式碼

2. 函式的bind

我們總會看到,函式可以這樣 test.bind(this),其中bind函式就是柯里化的思想

Funtion.prototype.bind()

var foo = {
    x: 888
}
var bar = function(e) {
    console.log(this.x, e)
}
Function.prototype.testBind = function(scope) {
    var fn = this;    // 指向的 bar
    return function() {
        return fn.apply(scope, [].slice.call(arguments))
    }
}

var test = bar.testBind(foo)    // 繫結 foo ,延遲執行
console.log(test)
test(2323)    //  執行, 結果是 888  2323
複製程式碼

將傳入的第一個引數(foo),當作之後函式的執行上下文,,,其他引數 再傳給 呼叫的方法(函式本身不執行,只進行了bind繫結,以後函式執行的時候,相當於了 延遲執行) , 所以相當於,預先繫結了物件並返回了函式,之後再執行函式,符合柯里化。

3.Redux中的 applyMiddle 中介軟體原理

let store = applyMiddleware(middleware1,middleware2)(createStore)(rootReducer);

export default function createLogger({ getState }) {
      return (next) =>  // return function
        (action) => {
              const console = window.console;
              const prevState = getState();
              const returnValue = next(action);
              const nextState = getState();
              const actionType = String(action.type);
              const message = `action ${actionType}`;

              console.log(`%c prev state`, `color: #9E9E9E`, prevState);
              console.log(`%c action`, `color: #03A9F4`, action);
              console.log(`%c next state`, `color: #4CAF50`, nextState);
              return returnValue;
    };
}
複製程式碼

作用總結

  • 一個js預先處理的思想;利用函式執行時可以形成一個不銷燬的作用域的原理,把需要預先處理的內容都儲存到這個不銷燬的作用域中,並且返回一個小函式,以後我們執行的都是這個小函式,小函式中把之前預先儲存的值進行相關操作處理。
  • 通常也成為部分求值,給函式分步傳遞引數,逐步縮小函式的適用範圍,逐步求解的過程。
  • 預處理
  • 延遲計算
  • 可以傳遞需要的引數,等到何時想要結果,再一併計算
  • 引數複用
  • 有些引數相同,只需要傳遞一遍即可,不需要每次都傳,太繁瑣。例如 bind
  • 動態建立函式。這可以是在部分計算出結果後,在此基礎上動態生成新的函式,處理後面的業務,這樣省略了重複計算。或者可以通過將要傳入呼叫函式的引數子集,部分應用到函式中,從而動態創造出一個新函式,這個新函式儲存了重複傳入的引數(以後不必每次都傳)。例如,瀏覽器新增事件的輔助方法。

最後:若有錯誤之處,還請見諒,提出後會馬上修改~~~

轉載請註明出處,謝謝~~

下一篇:DOM的解析和渲染與JS、CSS的關係

相關文章