JavaScript中的函數式編程

函數式編程

函數式編程是一種編程範式,是一種構建計算機程序結構和元素的風格,它把計算看做是對數學函數的評估,避免了狀態的變化和數據的可變,與函數式編程相對的是命令式編程。咱們有這樣一個需求,給數組的每一個數字加一:javascript

// 數組每一個數字加一, 命令式編程
let arr = [1, 2, 3, 4];
let newArr = [];
for(let i = 0; i < arr.length; i++){
  newArr.push(arr[i] + 1);
}

console.log(newArr); // [2, 3, 4, 5]

這段代碼結果沒有問題,可是無法重用。咱們換一個思惟,這裏麪包含的操做其實就兩個,一個是遍歷數組,一個是成員加一。咱們把這兩個方法拆出來:java

// 先拆加一出來
let add1 = x => x +1;

// 而後拆遍歷方法出來,經過遍歷返回一個操做後的新數組
// fn是咱們須要對每一個數組想進行的操做
let createArr = (arr, fn) => {
  const newArr = [];
  for(let i = 0; i < arr.length; i++){
    newArr.push(fn(arr[i]));
  }

  return newArr;
} 

// 用這兩個方法來獲得咱們指望的結果
const arr = [1, 2, 3, 4];
const newArr = createArr(arr, add1);
console.log(newArr);  // [2, 3, 4, 5], 結果仍然是對的

這樣拆分後,若是咱們下次的需求是對數組每一個元素乘以2,咱們只須要寫一個乘法的方法,而後複用以前的代碼就行:git

let multiply2 = x => x * 2;

// 調用以前的createArr
const arr2 = [1, 2, 3, 4];
const newArr2 = createArr(arr2, multiply2);
console.log(newArr2);  // [2, 4, 6, 8], 結果是對的

事實上咱們的加一函數只能加一,也很差複用,它還能夠繼續拆:github

// 先寫一個通用加法,他接收第一個加數,返回一個方法
// 返回的這個方法接收第二個加數,第一個加數是上層方法的a
// 這樣當咱們須要計算1+2是,就是add(1)(2)
let add = (a) => {
  return (b) => {
    return a + b;
  }
}

// 咱們也能夠將返回的函數賦給一個變量,這個變量也就變成一個能特定加a的一個方法
let add1 = add(1);

let res = add1(4); 
console.log(res);  // 5

因此函數式編程就是將程序分解爲一些更可重用、更可靠且更易於理解的部分,而後將他們組合起來,造成一個更易推理的程序總體。編程

純函數

純函數是指一個函數,若是它的調用參數相同,則永遠返回相同的結果。它不依賴於程序執行期間函數外部任何狀態或數據的變化,只依賴於其輸入參數。同時函數的運行也不改變任何外部數據,它只經過它的返回值與外部通信。數組

下面這個函數就不是純函數,由於函數內部須要的discount須要從外部獲取:函數式編程

let discount = 0.8;
const calPrice = price => price * discount;
let price = calPrice(200);  // 160

// 當discount變了,calPrice傳一樣額參數,結果不同,因此不純
discount = 0.9;
price = calPrice(200);  // 180

要改成純函數也很簡單,將discount做爲參數傳遞進去就好了函數

const calPrice = (price, discount) => price * discount;

純函數能夠保證代碼的穩定性,由於相同的輸入永遠會獲得相同結果。不純的函數可能會帶來反作用。spa

函數反作用

函數反作用是指調用函數時除了返回函數值以外,還對主調用函數產生附加的影響,好比修改全局變量或者外部變量,或者修改參數。這可能會帶來難以查找的問題並下降代碼的可讀性。下面的foo就有反作用,當後面有其餘地方須要使用a,可能就會拿到一個被污染的值code

let a = 5;
let foo = () => a = a * 10;
foo();
console.log(a); // 50

除了咱們本身寫的函數有反作用外,一些原生API也可能有反作用,咱們寫代碼時應該注意:

image-20200109232215022

咱們的目標是儘量的減小反作用,將函數寫爲純函數,下面這個不純的函數使用了new Date,每次運行結果不同,是不純的:

image-20200109232541307

要給爲純函數能夠將依賴注入進去,所謂依賴注入就是將不純的部分提取出來做爲參數,這樣咱們可讓反作用代碼集中在外部,遠離核心代碼,保證核心代碼的穩定性

// 依賴注入
const foo = (d, log, something) => {
  const dt = d.toISOString();
  return log(`${dt}: ${something}`);
}

const something = 'log content';
const d = new Date();
const log = console.log.bind(console);
foo(d, log, something);

因此減小反作用通常的方法就是:

1. 函數使用參數進行運算,不要修改參數
2. 函數內部不修改外部變量
3. 運算結果經過返回值返回給外部

可變性和不可變性

  • 可變性:指一個變量建立之後能夠任意修改
  • 不可變性: 指一個變量被建立後永遠不會發生改變,不可變性是函數式編程的核心概念

下面是一個可變的例子:

image-20200109233313733

若是咱們必定要修改這個參數,咱們應該將這個參數進行深拷貝後再操做,這樣就不會修改參數了:

image-20200109233515929

文章的最後,感謝你花費寶貴的時間閱讀本文,若是本文給了你一點點幫助或者啓發,請不要吝嗇你的贊和GitHub小星星,你的支持是做者持續創做的動力。

做者博文GitHub項目地址: https://github.com/dennis-jiang/Front-End-Knowledges

相關文章
相關標籤/搜索