Javascript中的柯里化

歡迎關注個人公衆號睿Talk,獲取我最新的文章:
clipboard.pngjavascript

1、前言

柯里化,是函數式編程的一個重要概念。對於沒接觸過的人來講,會被一串串的小括號弄得摸不着頭腦。但一旦理解了其中的含義和具體的使用場景,你必定會對它愛不釋手。它既能減小代碼冗餘,也能增長可讀性,可謂程序猿居家旅行,裝逼撕逼必備之良藥。java

2、什麼是柯里化

若是一個函數能夠接收多個參數,將這個函數轉化爲每次只接收一部分參數的函數的屢次調用形式,就是柯里化。文字上理解比較困難,先來看看下面的代碼:react

function add(a, b, c) {
    return a + b + c;
}

這個add函數接收3個參數,返回3個參數相加的結果。能夠經過如下2種形式對其進行柯里化:編程

function addOne(a) {
    return function(b) {
        return function(c) {
            return a + b + c;
            }
        }
    }
}

function addTwo(a,b) {
    return function(c) {
        return a + b + c;
    }
}

執行的時候,如下3種方式都會得到同樣的結果:redux

add(1, 2, 3);        // return 6
addOne(1)(2)(3);     // return 6
addTwo(1, 2)(3);     // return 6

若是使用ES6語法,能更簡潔的寫出柯里化後的函數,以addOne爲例:segmentfault

const addOne = (a) => (b) => (c) => (a + b + c)

上面的例子沒有什麼實際的意義,只是爲了說明概念而已。在瞭解基本概念後,咱們來聊聊實際的使用場景。數組

3、使用場景

  • 場景1: 性能優化

能夠將一些模板代碼經過柯里化的形式預先定義好,例如:瀏覽器

var addEvent = function(el, type, fn, capture) {
    if (window.addEventListener) {
        el.addEventListener(type, function(e) {
            fn.call(el, e);
        }, capture);
    } else if (window.attachEvent) {
        el.attachEvent("on" + type, function(e) {
            fn.call(el, e);
        });
    } 
};

addEvent(element, "click", handleClick, true);

這段代碼的做用就是根據瀏覽器的類型決定事件添加的方式。實際上if...else的判斷只須要進行一次。將它柯里化後能夠獲得如下結果:性能優化

function addCrossBrowserEvent() {
    if (window.addEventListener) {
        return function(el, sType, fn, capture) {
            el.addEventListener(sType, function(e) {
                fn.call(el, e);
            }, (capture));
        };
    } else if (window.attachEvent) {
        return function(el, sType, fn, capture) {
            el.attachEvent("on" + sType, function(e) {
                fn.call(el, e);
            });
        };
    }
}

var addEvent = addCrossBrowserEvent();

addEvent(element, "click", handleClick, true);
  • 場景2: 代碼複用

在有回調函數的場景下,能夠經過柯里化傳入一些預設的值,排列組合後,達到代碼複用的效果:app

// 匹配任何正則的函數
const match = (reg) => (str) => str.match(reg);
// 專門匹配空格
const hasSpace = match(/\s+/g);

// 封裝數組的 filter 方法
const filter = (fn) => (arr) => arr.filter(fn);
// 專門找空格的 filter 方法
const findSpace = filter(hasSpace);

// 使用
let result = findSpace(['hi man', 'hi_man']);    //['hi man']

使用傳統的方法實現以上效果,以下:

let result = ['hi man', 'hi_man'].filter( (item) => (item.match(/\s+/g)) );

雖然傳統的方法看起來代碼量比較少,但若是在不少地方須要使用的時候,就體現出封裝的威力了。並且,還能夠爲filter方法傳入其它的條件生成各式各樣的find工具函數!

  • 場景3: 使代碼便於理解

react-redux的connect方法,就是使用了柯里化增長代碼的可讀性:

let Container = connect(mapStateToProps, mapDispatchToProps)(Component);

在這裏,connect的做用就是將Component要用到的state切面和action注入到它的property中,達到展現型組件和容器組件分離的目的。若是將這個方法的定義改成:

let Container = connect(mapStateToProps, mapDispatchToProps, Component);

就沒那麼好理解了。並且,mapStateToProps和mapDispatchToProps實際上也是可選參數,在不傳它們的狀況下傳入Component會顯得很噁心: connect(null, null, Component)。

  • 場景4: 擴展Javascript能力

ES5中的bind方法,就是經過柯里化實現的:

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          return fToBind.apply(this instanceof fNOP
                 ? this
                 : oThis,
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    if (this.prototype) {
      fNOP.prototype = this.prototype; 
    }
    fBound.prototype = new fNOP();

    return fBound;
  };
}

4、總結

經過本文的介紹,相信你對柯里化已經有一個全新的認識了。它最少有如下4種功能:

  • 性能優化
  • 代碼複用
  • 使代碼便於理解
  • 擴展Javascript能力

靈活使用柯里化,提升代碼質量不是夢!

P.S. 若是還有本文沒有提到的柯里化用法,歡迎留言交流(^-^)

相關文章
相關標籤/搜索