前端之函數柯里化Currying

什麼是柯里化

在計算機科學中,柯里化(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
複製代碼

固然也能夠這樣寫(非柯里化)bash

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),即每次接受一個參數並放回一個函數,而後鏈式的執行。。。app

能夠看到,adder函數中有着內部函數,內部函數一直引用着n,這就造成了一個閉包,因此柯里化是閉包的應用之一,面試時直接能夠答~~~函數

進階

從上面能夠大概瞭解了,柯里化就是,post

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

注意:最後一次調用沒有參數(),因此adder中能夠經過判斷是否有參數,去判斷我是要繼續相加,仍是返回求和值了this

或是這樣也能夠

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

通用封裝代碼以下,必定要搞懂!

實際應用

若是仍是沒以爲有啥用,看兩個實際的例子

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的關係

相關文章
相關標籤/搜索