原做者:一名來自臺灣的小夥子,熱愛學習新技術,喜歡 JS 與 Functional Programming,熱衷於把困難的技術用簡單的語言闡述,歡迎來到個人文章。html
原文前端
Functional Programming是一種編程規範(programming paradigm),就像Object-oriented Programming(OOP)同樣,就是一種寫程序的方法論,這些方法論告訴咱們如何思考及解決問題。express
簡單說Functional Programming核心思想就是作運算處理,並用function來思考問題,例如像如下的算數表達式:編程
(5 + 6) - 1 * 3
複製代碼
咱們能夠寫成數組
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))
複製代碼
咱們把每一個運算包成一個個不一樣的function,並用這些function組合出咱們要的結果,這就是最簡單的Functional Programming。bash
跟OOP同樣不是全部的語言都支持FP,要可以支持FP的語言至少須要符合函數爲一等公民的特性。ide
一等公民就是指跟其餘數據類型具備同等地位,也就是說函數可以被賦值給變量,函數也可以被看成參數傳入另外一個函數,也可看成一個函數的返回值函數
var hello = function() {}
複製代碼
fetch('www.google.com')
.then(function(response) {}) // 匿名 function 被傳入 then()
複製代碼
var a = function(a) {
return function(b) {
return a + b;
};
// 能夠返回一個 function
}
複製代碼
Functional Programming都是表達式(Expression)不會是聲明式(Statement)。基本區分表達式與聲明式:單元測試
表達式是一個運算過程,必定會有返回值,例如執行一個function學習
add(1,2)
複製代碼
聲明式 則是表現某個行爲,例如一個賦值給一個變數
a = 1;
複製代碼
有時候表達式也可能同時是合法的聲明式,這裏只講基本的判斷方法。若是想更深刻了解其中的差別,能夠看這篇文章 Expressions versus statements in JavaScript
因爲Functional Programming最先就是爲了作運算處理無論I/O,而Statement一般都屬於對系統I/O的操做,因此FP很天然的不會是Statement。
固然在事務中不可能徹底沒有I/O的操做,Functional Programming只要求對I/O操做限制到最小,不要有沒必要要的I/O行爲,儘可能保持運算過程的單純。
舉個例子:
var arr = [1, 2, 3, 4, 5];
arr.slice(0, 3); // [1, 2, 3]
arr.slice(0, 3); // [1, 2, 3]
arr.slice(0, 3); // [1, 2, 3]
複製代碼
這裏能夠看到slice無論執行幾回,返回值都是相同的,而且除了返回一個值(value)以外並無作任何事,因此slice就是一個pure function。
var arr = [1, 2, 3, 4, 5];
arr.splice(0, 3); // [1, 2, 3]
arr.splice(0, 3); // [4, 5]
arr.slice(0, 3); // []
複製代碼
這裏咱們換成用splice,由於splice每執行一次就會影響arr的值,致使每次結果都不一樣,這就很明顯不是一個pure function。
Side Effect是指一個function作了跟自己運算返回值沒有關係的事,好比說修改某個全局變數,或是修改傳入參數的值,甚至是執行console.log都算是Side Effect。
Functional Programming強調沒有Side Effect,也就是function要保持純粹,只作運算並返回一個值,沒有其餘額外的行爲。
這裏列舉幾個前端常見的Side Effect,但不是所有
發送http request
在畫面輸出或是log
得到使用者input
Query DOM對象
前面提到的pure function無論外部環境如何,只要參數相同,函數執行的返回結果一定相同。這種不依賴任何外部狀態,只依賴於傳入的參數的特性也稱爲引用透明(Referential transparency)
因爲最近很紅的Redux使我能很好的舉例,讓你們瞭解什麼是用參數保存狀態。瞭解Redux的開發者應該會知Redux的狀態是由各個reducer所組成的,而每一個reducer的狀態就是保存在參數中!
function countReducer(state = 0, action) {
// ...
}
複製代碼
若是你跟Redux不熟能夠看下面遞迴的例子
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' 的 index
複製代碼
這裏咱們寫了一個findIndex用來找數組中的元素位置,咱們在findIndex中故意多塞了一個參數用來保存當前找到第幾個index的狀態,這就是利用參數保存狀態!
這邊用到了遞歸,遞歸會不斷的調用本身,製造多層stack frame,會致使運算速度較慢,而這一般須要靠編譯器作優化!
那JS有沒有作遞歸優化呢?恭喜你們,ES6提供了尾調用優化(tail call optimization),讓咱們有一些手法可讓遞歸更有效率!
當咱們經過一系列的函數封裝數據的操做過程,代碼能變得很是的簡潔且可讀性極高,例以下面的例子
[9, 4].concat([8, 7]) // 合併數組
.sort() // 排序
.filter(x => x > 5) // 過濾出大於 5 的
複製代碼
由於Pure function等特性,執行結果不依賴外部狀態,且不會對外部環境有任何操做,使Functional Programming能更好的排錯及編寫單元測試。
Functional Programming易於作並行/平行(Concurrency/Parallel)處理,由於咱們基本上只作運算不碰I/O,再加上沒有Side Effect的特性,因此較不用擔憂deadlock等問題。
今天講了Functional Programming的基本特性,及其優點。如今越來越多的Library用到了FP的觀念,JS也愈來愈多Functional的函數庫,例如:Lodash,Underscore,lazy,Ramda。瞭解FP的基本觀念有助於咱們在學習其餘Library更容易上手,也能使咱們寫出更好的代碼,但願各位讀者有所收穫,如有任何疑問歡迎在下方留言給我!