JavaScript 中的函數式編程

一、是什麼

函數式編程是一種 "編程範式"(programming paradigm),一種編寫程序的方法論

主要的編程範式有三種:命令式編程,聲明式編程和函數式編程

相比命令式編程,函數式編程更加強調程序執行的結果而非執行的過程,倡導利用若干簡單的執行單元讓計算結果不斷漸進,逐層推導複雜的運算,而非設計一個複雜的執行過程

舉個例子,將數組每個元素進行平方操作,命令式編程與函數式編程如下

// 命令式編程
var array = [0, 1, 2, 3]
for(let i = 0; i < array.length; i++) {
    array[i] = Math.pow(array[i], 2)
}

// 函數式方式
[0, 1, 2, 3].map(num => Math.pow(num, 2))

簡單來講,就是要把過程邏輯寫成函數,定義好輸入參數,只關心它的輸出結果

即是一種描述集合和集合之間的轉換關係,輸入通過函數都會返回有且只有一個輸出值

可以看到,函數實際上是一個關係,或者說是一種映射,而這種映射關係是可以組合的,一旦我們知道一個函數的輸出類型可以匹配另一個函數的輸入,那他們就可以進行組合

二、概念

純函數

函數式編程旨在儘可能的提高代碼的無狀態性和不變性。要做到這一點,就要學會使用無副作用的函數,也就是純函數

純函數是對給定的輸入返還相同輸出的函數,並且要求你所有的數據都是不可變的,即純函數 = 無狀態 + 數據不可變

舉一個簡單的例子

let double = value=>value*2;

特性:

優勢:

test('double(2) 等於 4'() ={
  expect(double(2)).toBe(4);
})

高階函數

在我們的編程世界中,我們需要處理的其實也只有 “數據” 和“關係”,而關係就是函數

編程工作也就是在找一種映射關係,一旦關係找到了,問題就解決了,剩下的事情,就是讓數據流過這種關係,然後轉換成另一個數據,如下圖所示

在這裏,就是高階函數的作用。高級函數,就是以函數作爲輸入或者輸出的函數被稱爲高階函數

通過高階函數抽象過程,注重結果,如下面例子

const forEach = function(arr,fn){
    for(let i=0;i<arr.length;i++){
        fn(arr[i]);
    }
}
let arr = [1,2,3];
forEach(arr,(item)=>{
    console.log(item);
})

上面通過高階函數 forEach來抽象循環如何做的邏輯,直接關注做了什麼

高階函數存在緩存的特性,主要是利用閉包作用

const once = (fn)=>{
    let done = false;
    return function(){
        if(!done){
            fn.apply(this,fn);
        }else{
            console.log("該函數已經執行");
        }
        done = true;
    }
}

柯里化

柯里化是把一個多參數函數轉化成一個嵌套的一元函數的過程

一個二元函數如下:

let fn = (x,y)=>x+y;

轉化成柯里化函數如下:

const curry = function(fn){
    return function(x){
        return function(y){
            return fn(x,y);
        }
    }
}
let myfn = curry(fn);
console.log( myfn(1)(2) );

上面的curry函數只能處理二元情況,下面再來實現一個實現多參數的情況

// 多參數柯里化;
const curry = function(fn){
    return function curriedFn(...args){
        if(args.length<fn.length){
            return function(){
                return curriedFn(...args.concat([...arguments]));
            }
        }
        return fn(...args);
    }
}
const fn = (x,y,z,a)=>x+y+z+a;
const myfn = curry(fn);
console.log(myfn(1)(2)(3)(1));

關於柯里化函數的意義如下:

組合與管道

組合函數,目的是將多個函數組合成一個函數

舉個簡單的例子:

function afn(a){
    return a*2;
}
function bfn(b){
    return b*3;
}
const compose = (a,b)=>c=>a(b(c));
let myfn =  compose(afn,bfn);
console.log( myfn(2));

可以看到compose實現一個簡單的功能:形成了一個新的函數,而這個函數就是一條從 bfn -> afn 的流水線

下面再來看看如何實現一個多函數組合:

const compose = (...fns)=>val=>fns.reverse().reduce((acc,fn)=>fn(acc),val);

compose執行是從右到左的。而管道函數,執行順序是從左到右執行的

const pipe = (...fns)=>val=>fns.reduce((acc,fn)=>fn(acc),val);

組合函數與管道函數的意義在於:可以把很多小函數組合起來完成更復雜的邏輯

三、優缺點

優點

缺點:

參考文獻

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Do8lTY9j0IB5PGZe0rQj8Q