簡單說,"函數式編程"是一種 "編程範式"(programming paradigm),也就是如何編寫程序的方法論。javascript
它屬於 "結構化編程" 的一種,主要思想是把運算過程儘可能寫成一系列嵌套的函數調用。舉例來講,如今有這樣一個數學表達式:html
(5+6) - 1 * 3複製代碼
傳統的過程式編程,可能這樣寫:前端
var a = 5 + 6;
var b = 1 * 3;
var c = a - b;複製代碼
函數式編程要求使用函數,咱們能夠把運算定義成不一樣的函數:java
const add = (a, b) => a + b;
const mul = (a, b) => a * b;
const sub = (a,b) => a - b;
sub(add(5,6), mul(1,3));複製代碼
咱們把每一個運算包成一個個不一樣的函數,而且根據這些函數組合出咱們要的結果,這就是最簡單的函數式編程。typescript
函數爲一等公民 (First Class)shell
所謂 "一等公民"(first class),指的是函數與其餘數據類型同樣,處於平等地位,能夠賦值給其餘變量,也能夠做爲參數,傳入另外一個函數,或者做爲其它函數的返回值。express
函數賦值給變量:編程
const greet = function(msg) { console.log(`Hello ${msg}`); }
greet('Semlinker'); // Output: 'Hello Semlinker'複製代碼
函數做爲參數:數組
const logger = function(msg) { console.log(`Hello ${msg}`); };
const greet = function(msg, print) { print(msg); };
greet('Semlinker', logger);複製代碼
函數做爲返回值:併發
const a = function(a) {
return function(b) {
return a + b;
};
};
const add5 = a(5);
add5(10); // Output: 15複製代碼
只用表達式,不用語句
"表達式"(expression)是一個單純的運算過程,老是有返回值;"語句"(statement)是執行某種操做,沒有返回值。函數式編程要求,只使用表達式,不使用語句。也就是說,每一步都是單純的運算,並且都有返回值。
緣由是函數式編程的開發動機,一開始就是爲了處理運算(computation),不考慮系統的讀寫(I/O)。"語句"屬於對系統的讀寫操做,因此就被排斥在外。
Pure Function
Pure Function (純函數) 的特色:
所謂 "反作用")(side effect),是指函數內作了與自己運算無關的事,好比修改某個全局變量的值,或發送 HTTP 請求,甚至函數體內執行 console.log
都算是反作用。函數式編程強調函數不能有反作用,也就是函數要保持純粹,只執行相關運算並返回值,沒有其餘額外的行爲。
前端中常見的產生反作用的場景:
接下來咱們看一下純函數與非純函數的具體示例:
純函數示例:
const double = (number) => number * 2;
double(5);複製代碼
非純函數示例:
Math.random(); // => 0.3384159509502669
Math.random(); // => 0.9498302571942787
Math.random(); // => 0.9860841663478281複製代碼
不修改狀態 - 利用參數保存狀態
函數式編程只是返回新的值,不修改系統變量。所以,不修改變量,也是它的一個重要特色。
在其餘類型的語言中,變量每每用來保存"狀態"(state)。不修改變量,意味着狀態不能保存在變量中。函數式編程使用參數保存狀態,最好的例子就是遞歸,具體示例以下:
function findIndex(arr, predicate, start = 0) {
if (0 <= start && start < arr.length) {
if (predicate(arr[start])) {
return start;
}
return findIndex(arr, predicate, start+1);
}
}
findIndex(['a', 'b'], x => x === 'b'); // 查找數組中'b'的索引值複製代碼
示例中的 findIndex 函數用於查找數組中某個元素的索引值,咱們經過 start 參數來保存當前的索引值,這就是利用參數保存狀態。
引用透明
引用透明(Referential transparency),指的是函數的運行不依賴於外部變量或 "狀態",只依賴於輸入的參數,任什麼時候候只要參數相同,引用函數所獲得的返回值老是相同的。
非引用透明的示例:
const FIVE = 5;
const addFive = (num) => num + FIVE;
addFive(10);複製代碼
1.代碼簡潔,開發快速
函數式編程大量使用函數,減小了代碼的重複,所以程序比較短,開發速度較快。
2.接近天然語言,易於理解,可讀性高
函數式編程的自由度很高,能夠寫出很接近天然語言的代碼。咱們能夠經過一系列的函數,封裝數據的處理過程,代碼會變得很是簡潔且可讀性高,具體參考如下示例:
[1,2,3,4,5].map(x => x * 2).filter(x => x > 5).reduce((p,n) => p + n);複製代碼
3.可維護性高、方便代碼管理
函數式編程不依賴、也不會改變外界的狀態,只要給定輸入參數,返回的結果一定相同。所以,每個函數均可以被看作獨立單元,頗有利於進行單元測試(unit testing)和除錯(debugging),以及模塊化組合。
4.易於"併發編程"
函數式編程不須要考慮"死鎖"(deadlock),由於它不修改變量,因此根本不存在"鎖"線程的問題。沒必要擔憂一個線程的數據,被另外一個線程修改,因此能夠很放心地把工做分攤到多個線程,部署"併發編程"(concurrency)。
forEach
在 ES 5 版本以前,咱們只能經過 for 循環遍歷數組:
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
for (var i =0, len = heroes.length; i < len; i++) {
console.log(heroes[i]);
}複製代碼
在 ES 5 版本以後,咱們可使用 forEach 方法,實現上面的功能:
forEach 方法簽名:
array.forEach(callback[, thisArg])複製代碼
參數說明:
以上示例 forEach 方法實現:
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
heroes.forEach(name => console.log(name));複製代碼
map
在 ES 5 版本以前,對於上面的示例,若是咱們想給每一個英雄的名字添加一個前綴,但不改變原來的數組,咱們能夠這樣實現:
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
var prefixedHeroes = [];
for (var i =0, len = heroes.length; i < len; i++) {
prefixedHeroes.push('Super_' + heroes[i]);
}複製代碼
在 ES 5 版本以後,咱們可使用 map 方法,方便地實現上面的功能。
map 方法簽名:
const new_array = arr.map(callback[, thisArg])複製代碼
參數說明:
以上示例 map 方法實現:
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
var prefixedHeroes = heroes.map(name => 'Super_' + name);複製代碼
filter
在 ES 5 版本以前,對於 heroes 數組,咱們想獲取名字中包含 m
字母的英雄,咱們能夠這樣實現:
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
var filterHeroes = [];
for (var i =0, len = heroes.length; i < len; i++) {
if(/m/i.test(heroes[i])) {
filterHeroes.push(heroes[i]);
}
}複製代碼
在 ES 5 版本以後,咱們可使用 filter 方法,方便地實現上面的功能。
filter 方法簽名:
var new_array = arr.filter(callback[, thisArg])複製代碼
參數說明:
以上示例 filter 方法實現:
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
var filterRe = /m/i;
var filterHeroes = heroes.filter(name => filterRe.test(name));複製代碼