函數語言程式設計
函數語言程式設計是一種程式設計正規化,是一種構建計算機程式結構和元素的風格,它把計算看作是對數學函式的評估,避免了狀態的變化和資料的可變,與函數語言程式設計相對的是指令式程式設計。我們有這樣一個需求,給陣列的每個數字加一:
// 陣列每個數字加一, 指令式程式設計
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]
這段程式碼結果沒有問題,但是沒法重用。我們換一個思維,這裡麵包含的操作其實就兩個,一個是遍歷陣列,一個是成員加一。我們把這兩個方法拆出來:
// 先拆加一出來
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,我們只需要寫一個乘法的方法,然後複用之前的程式碼就行:
let multiply2 = x => x * 2;
// 呼叫之前的createArr
const arr2 = [1, 2, 3, 4];
const newArr2 = createArr(arr2, multiply2);
console.log(newArr2); // [2, 4, 6, 8], 結果是對的
事實上我們的加一函式只能加一,也不好複用,它還可以繼續拆:
// 先寫一個通用加法,他接收第一個加數,返回一個方法
// 返回的這個方法接收第二個加數,第一個加數是上層方法的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;
純函式可以保證程式碼的穩定性,因為相同的輸入永遠會得到相同結果。不純的函式可能會帶來副作用。
函式副作用
函式副作用是指呼叫函式時除了返回函式值之外,還對主呼叫函式產生附加的影響,比如修改全域性變數或者外部變數,或者修改引數。這可能會帶來難以查詢的問題並降低程式碼的可讀性。下面的foo
就有副作用,當後面有其他地方需要使用a,可能就會拿到一個被汙染的值
let a = 5;
let foo = () => a = a * 10;
foo();
console.log(a); // 50
除了我們自己寫的函式有副作用外,一些原生API也可能有副作用,我們寫程式碼時應該注意:
我們的目標是儘可能的減少副作用,將函式寫為純函式,下面這個不純的函式使用了new Date
,每次執行結果不一樣,是不純的:
要給為純函式可以將依賴注入進去,所謂依賴注入就是將不純的部分提取出來作為引數,這樣我們可以讓副作用程式碼集中在外部,遠離核心程式碼,保證核心程式碼的穩定性
// 依賴注入
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. 運算結果通過返回值返回給外部
可變性和不可變性
- 可變性:指一個變數建立以後可以任意修改
- 不可變性: 指一個變數被建立後永遠不會發生改變,不可變性是函數語言程式設計的核心概念
下面是一個可變的例子:
如果我們一定要修改這個引數,我們應該將這個引數進行深拷貝後再操作,這樣就不會修改引數了:
文章的最後,感謝你花費寶貴的時間閱讀本文,如果本文給了你一點點幫助或者啟發,請不要吝嗇你的贊和GitHub小星星,你的支援是作者持續創作的動力。
作者博文GitHub專案地址: https://github.com/dennis-jiang/Front-End-Knowledges