JavaScript 函數式編程

函數式編程具備兩個基本特徵。編程

  • 函數是第一等公民
  • 函數是純函數

函數是第一等公民

第一等公民是指函數跟其它的數據類型同樣處於平等地位,能夠賦值給其餘變量,能夠做爲參數傳入另外一個函數,也能夠做爲別的函數的返回值。數據結構

// 賦值
var a = function fn1() {  }
// 函數做爲參數
function fn2(fn) {
    fn()
}   
// 函數做爲返回值
function fn3() {
    return function() {}
}

函數是純函數

純函數是指相同的輸入總會獲得相同的輸出,而且不會產生反作用的函數。app

從純函數的概念咱們能夠知道純函數具備兩個特色:dom

  • 同輸入同輸出
  • 無反作用

無反作用指的是函數內部的操做不會對外部產生影響(如修改全局變量的值、修改 dom 節點等)。函數式編程

// 是純函數
function add(x,y){
    return x + y
}
// 輸出不肯定,不是純函數
function random(x){
    return Math.random() * x
}
// 有反作用,不是純函數
function setColor(el,color){
    el.style.color = color ;
}
// 輸出不肯定、有反作用,不是純函數
var count = 0;
function addCount(x){
    count+=x;
    return count;
}

函數式編程具備兩個最基本的運算:合成(compose)和柯里化(Currying)。函數

函數合成(compose)

函數合成指的是將表明各個動做的多個函數合併成一個函數。
這裏我直接給出通用 compose 函數的代碼post

function compose() {
    var args = arguments;
    var start = args.length - 1;
    return function() {
        var i = start;
        var result = args[start].apply(this, arguments);
        while (i--) result = args[i].call(this, result);
        return result;
    };
}

讓咱們來實踐下上述通用的 compose 函數~優化

function addHello(str){
    return 'hello '+str;
}
function toUpperCase(str) {
    return str.toUpperCase();
}
function reverse(str){
    return str.split('').reverse().join('');
}

var composeFn=compose(reverse,toUpperCase,addHello);

console.log(composeFn('ttsy'));  // YSTT OLLEH

上述過程有三個動做,「hello」、「轉換大寫」、「反轉」,能夠看到經過 compose 將上述三個動做表明的函數合併成了一個,最終輸出了正確的結果。this

函數柯里化(Currying)

在維基百科中對柯里化的定義是:在計算機科學中,柯里化,又譯爲卡瑞化或加里化,是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。prototype

柯里化函數則是將函數柯里化以後獲得的一個新函數。由上述定義可知,柯里化函數有以下兩個特性:

  • 接受一個單一參數;
  • 返回接受餘下的參數並且返回結果的新函數;

優化後的 createCurry 代碼以下:

// 參數只能從左到右傳遞
function createCurry(func, arrArgs) {
    var args=arguments;
    var funcLength = func.length;
    var arrArgs = arrArgs || [];

    return function() {
        var _arrArgs = Array.prototype.slice.call(arguments);
        var allArrArgs=arrArgs.concat(_arrArgs)

        // 若是參數個數小於最初的func.length,則遞歸調用,繼續收集參數
        if (allArrArgs.length < funcLength) {
            return args.callee.call(this, func, allArrArgs);
        }

        // 參數收集完畢,則執行func
        return func.apply(this, allArrArgs);
    }
}

優化以後的 createCurry 函數則顯得更增強大

// createCurry 返回一個柯里化函數
var addCurry=createCurry(function(a, b, c) {
    return a + b + c;
});

console.log(addCurry(1)(2)(3));  // 6
console.log(addCurry(1, 2, 3));  // 6
console.log(addCurry(1, 2)(3));  // 6
console.log(addCurry(1)(2, 3));  // 6

柯里化其實是把簡答的問題複雜化了,可是複雜化的同時,咱們在使用函數時擁有了更加多的自由度。

函子

在前面函數合成的例子中,執行了先「加上 4」再「乘以 4」的動做,咱們能夠看到代碼中是經過 multiply4(add4(1)) 這種形式來實現的,若是經過 compose 函數,則是相似於 compose(multiply4,add4)(1) 這種形式來實現代碼。

而在函數式編程的思惟中,除了將動做抽象出來外,還但願動做執行的順序更加清晰,因此對於上面的例子來講,更但願是經過以下的形式來執行咱們的動做

fn(1).add4().multiply4()

這時咱們須要用到函子的概念。

function Functor(val){
    this.val = val;
}
Functor.prototype.map=function(f){
    return new Functor(f(this.val));
}

函子能夠簡單地理解爲有用到 map 方法的數據結構。如上 Functor 的實例就是一個函子。

在函子的 map 方法中接受一個函數參數,而後返回一個新的函子,新的函子中包含的值是被函數參數處理事後返回的值。該方法將函子裏面的每個值,映射到另外一個函子。

經過 Functor 函子,咱們能夠經過以下的方式調用

console.log((new Functor(1)).map(add4).map(multiply4))  // Functor { val: 20 }

上述調用的方式是 (new Calculate(1)).map(add4).map(multiply4) ,跟咱們想要的效果已經差很少了,可是咱們不但願有 new 的存在,因此咱們在 Functor 函子掛載上 of 方法

function Functor(val){
    this.val = val;
}
Functor.prototype.map=function(f){
    return new Functor(f(this.val));
}
Functor.of = function(val) {
    return new Functor(val);
}

最終咱們能夠經過以下方式調用

console.log(Functor.of(1).map(add4).map(multiply4))  // Functor { val: 20 }

接下來介紹各類常見的函子。

Maybe 函子

Maybe 函子是指在 map 方法中增長了對空值的判斷的函子。

因爲函子中的 map 方法中的函數參數會對函子內部的值進行處理,因此當傳入函子中的值爲空(如 null)時,則可能會產生錯誤。

function toUpperCase(str) {
    return str.toUpperCase();
}

console.log(Functor.of(null).map(toUpperCase));  // TypeError

Maybe 函子則在 map 方法中增長了對空值的判斷,如果函子內部的值爲空,則直接返回一個內部值爲空的函子。

function Maybe(val){
    this.val = val;
}
Maybe.prototype.map=function(f){
    return this.val ? Maybe.of(f(this.val)) : Maybe.of(null);
}
Maybe.of = function(val) {
    return new Maybe(val);
}

當使用 Maybe 函子時傳入空值則不會報錯

console.log(Maybe.of(null).map(toUpperCase));  // Maybe { val: null }

Either 函子

Either 函子是指內部有分別有左值(left)和右值(right),正常狀況下會使用右值,而當右值不存在的時候會使用左值的函子。

function Either(left,right){
    this.left = left;
    this.right = right;
}
Either.prototype.map=function(f){
    return this.right ? Either.of(this.left, f(this.right)) : Either.of(f(this.left), this.right);
}
Either.of = function(left,right) {
    return new Either(left,right);
}

以下當左右值都存在的時候則以右值爲函子的默認值,當右值不存在是則以左值爲函子的默認值。

function addOne(x) {
    return x+1;
}

console.log(Either.of(1,2).map(addOne));  // Either { left: 1, right: 3 }
console.log(Either.of(3,null).map(addOne));  // Either { left: 4, right: null }

Monad 函子

Monad 函子是指可以將函子多層嵌套解除的函子。

咱們往函子傳入的值不只僅能夠是普通的數據類型,也能夠是其它函子,當往函子內部傳其它函子的時候,則會出現函子的多層嵌套。以下

var functor = Functor.of(Functor.of(Functor.of('ttsy')))

console.log(functor);  // Functor { val: Functor { val: Functor { val: 'ttsy' } } }
console.log(functor.val);  // Functor { val: Functor { val: 'ttsy' } }
console.log(functor.val.val);  // Functor { val: 'ttsy' }
Monad 函子中新增了 join 和 flatMap 方法,經過 flatMap 咱們可以在每一次傳入函子的時候都將嵌套解除。

Monad.prototype.map=function(f){
    return Monad.of(f(this.val))
}
Monad.prototype.join=function(){
    return this.val;
}
Monad.prototype.flatMap=function(f){
    return this.map(f).join();
}
Monad.of = function(val) {
    return new Monad(val);
}

經過 Monad 函子,咱們最終獲得的都是隻有一層的函子。

console.log(Monad.of('ttsy').flatMap(Monad.of).flatMap(Monad.of));  // Monad { val: 'TTSY' }

在咱們平時的開發過程當中,要根據不一樣的場景去實現不一樣功能的函數,而函數式編程則讓咱們從不一樣的角度去讓咱們可以以最佳的方式去實現函數功能,但函數式編程不是非此即彼的,而是要根據不一樣的應用場景去選擇不一樣的實現方式。

摘自掘金:http://www.javashuo.com/article/p-embzophk-y.html

做者:劉偉波

連接:http://www.liuweibo.cn/p/207

來源:劉偉波博客

相關文章
相關標籤/搜索