javascript
RxJS Functional Programming
什么是函數(shù)式編程
簡單說,"函數(shù)式編程"是一種 "編程范式"(programming paradigm),也就是如何編寫程序的方法論。
它屬于 "結(jié)構(gòu)化編程" 的一種,主要思想是把運(yùn)算過程盡量寫成一系列嵌套的函數(shù)調(diào)用。舉例來說,現(xiàn)在有這樣一個(gè)數(shù)學(xué)表達(dá)式:
(5+6) - 1 * 3傳統(tǒng)的過程式編程,可能這樣寫:
var a = 5 + 6; var b = 1 * 3; var c = a - b;函數(shù)式編程要求使用函數(shù),我們可以把運(yùn)算定義成不同的函數(shù):
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));我們把每個(gè)運(yùn)算包成一個(gè)個(gè)不同的函數(shù),并且根據(jù)這些函數(shù)組合出我們要的結(jié)果,這就是最簡單的函數(shù)式編程。
函數(shù)式編程基礎(chǔ)條件
函數(shù)為一等公民 (First Class)
所謂 "一等公民"(first class),指的是函數(shù)與其他數(shù)據(jù)類型一樣,處于平等地位,可以賦值給其他變量,也可以作為參數(shù),傳入另一個(gè)函數(shù),或者作為其它函數(shù)的返回值。
函數(shù)賦值給變量:
const greet = function(msg) { console.log(`Hello ${msg}`); } greet('Semlinker'); // Output: 'Hello Semlinker'函數(shù)作為參數(shù):
const logger = function(msg) { console.log(`Hello ${msg}`); }; const greet = function(msg, print) { print(msg); }; greet('Semlinker', logger);函數(shù)作為返回值:
const a = function(a) {return function(b) {return a + b;}; }; const add5 = a(5); add5(10); // Output: 15函數(shù)式編程重要特性
只用表達(dá)式,不用語句
"表達(dá)式"(expression)是一個(gè)單純的運(yùn)算過程,總是有返回值;"語句"(statement)是執(zhí)行某種操作,沒有返回值。函數(shù)式編程要求,只使用表達(dá)式,不使用語句。也就是說,每一步都是單純的運(yùn)算,而且都有返回值。
原因是函數(shù)式編程的開發(fā)動機(jī),一開始就是為了處理運(yùn)算(computation),不考慮系統(tǒng)的讀寫(I/O)。"語句"屬于對系統(tǒng)的讀寫操作,所以就被排斥在外。
Pure Function
Pure Function (純函數(shù)) 的特點(diǎn):
給定相同的輸入?yún)?shù),總是返回相同的結(jié)果
沒有產(chǎn)生任何副作用
沒有依賴外部變量的值
所謂 "副作用")(side effect),是指函數(shù)內(nèi)做了與本身運(yùn)算無關(guān)的事,比如修改某個(gè)全局變量的值,或發(fā)送 HTTP 請求,甚至函數(shù)體內(nèi)執(zhí)行 console.log 都算是副作用。函數(shù)式編程強(qiáng)調(diào)函數(shù)不能有副作用,也就是函數(shù)要保持純粹,只執(zhí)行相關(guān)運(yùn)算并返回值,沒有其他額外的行為。
前端中常見的產(chǎn)生副作用的場景:
發(fā)送 HTTP 請求
函數(shù)內(nèi)調(diào)用 logger 函數(shù),如 console.log、console.dir 等
修改外部變量的值
函數(shù)內(nèi)執(zhí)行 DOM 操作
接下來我們看一下純函數(shù)與非純函數(shù)的具體示例:
純函數(shù)示例:
const double = (number) => number * 2; double(5);非純函數(shù)示例:
Math.random(); // => 0.3384159509502669 Math.random(); // => 0.9498302571942787 Math.random(); // => 0.9860841663478281不修改狀態(tài) - 利用參數(shù)保存狀態(tài)
函數(shù)式編程只是返回新的值,不修改系統(tǒng)變量。因此,不修改變量,也是它的一個(gè)重要特點(diǎn)。
在其他類型的語言中,變量往往用來保存"狀態(tài)"(state)。不修改變量,意味著狀態(tài)不能保存在變量中。函數(shù)式編程使用參數(shù)保存狀態(tài),最好的例子就是遞歸,具體示例如下:
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'); // 查找數(shù)組中'b'的索引值示例中的 findIndex 函數(shù)用于查找數(shù)組中某個(gè)元素的索引值,我們通過 start 參數(shù)來保存當(dāng)前的索引值,這就是利用參數(shù)保存狀態(tài)。
引用透明
引用透明(Referential transparency),指的是函數(shù)的運(yùn)行不依賴于外部變量或 "狀態(tài)",只依賴于輸入的參數(shù),任何時(shí)候只要參數(shù)相同,引用函數(shù)所得到的返回值總是相同的。
非引用透明的示例:
const FIVE = 5; const addFive = (num) => num + FIVE; addFive(10);函數(shù)式編程的優(yōu)勢
1.代碼簡潔,開發(fā)快速
函數(shù)式編程大量使用函數(shù),減少了代碼的重復(fù),因此程序比較短,開發(fā)速度較快。
2.接近自然語言,易于理解,可讀性高
函數(shù)式編程的自由度很高,可以寫出很接近自然語言的代碼。我們可以通過一系列的函數(shù),封裝數(shù)據(jù)的處理過程,代碼會變得非常簡潔且可讀性高,具體參考以下示例:
[1,2,3,4,5].map(x => x * 2).filter(x => x > 5).reduce((p,n) => p + n);3.可維護(hù)性高、方便代碼管理
函數(shù)式編程不依賴、也不會改變外界的狀態(tài),只要給定輸入?yún)?shù),返回的結(jié)果必定相同。因此,每一個(gè)函數(shù)都可以被看做獨(dú)立單元,很有利于進(jìn)行單元測試(unit testing)和除錯(cuò)(debugging),以及模塊化組合。
4.易于"并發(fā)編程"
函數(shù)式編程不需要考慮"死鎖"(deadlock),因?yàn)樗恍薷淖兞?#xff0c;所以根本不存在"鎖"線程的問題。不必?fù)?dān)心一個(gè)線程的數(shù)據(jù),被另一個(gè)線程修改,所以可以很放心地把工作分?jǐn)偟蕉鄠€(gè)線程,部署"并發(fā)編程"(concurrency)。
函數(shù)式編程中常用方法
forEach
在 ES 5 版本之前,我們只能通過 for 循環(huán)遍歷數(shù)組:
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado']; for (var i =0, len = heroes.length; i < len; i++) {console.log(heroes[i]); }在 ES 5 版本之后,我們可以使用 forEach 方法,實(shí)現(xiàn)上面的功能:
forEach 方法簽名:
array.forEach(callback[, thisArg])參數(shù)說明:
-
callback - 對數(shù)組中每一項(xiàng),進(jìn)行處理的函數(shù)
currentValue - 數(shù)組中正在處理的當(dāng)前元素
index - 數(shù)組中正在處理的當(dāng)前元素的索引
array - 處理的數(shù)組
thisArg (可選的) - 設(shè)置執(zhí)行 callback 函數(shù)時(shí),this 的值
以上示例 forEach 方法實(shí)現(xiàn):
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado']; heroes.forEach(name => console.log(name));map
在 ES 5 版本之前,對于上面的示例,如果我們想給每個(gè)英雄的名字添加一個(gè)前綴,但不改變原來的數(shù)組,我們可以這樣實(shí)現(xiàn):
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 方法,方便地實(shí)現(xiàn)上面的功能。
map 方法簽名:
const new_array = arr.map(callback[, thisArg])參數(shù)說明:
-
callback - 對數(shù)組中每一項(xiàng),進(jìn)行映射處理的函數(shù)
currentValue - 數(shù)組中正在處理的當(dāng)前元素
index - 數(shù)組中正在處理的當(dāng)前元素的索引
array - 處理的數(shù)組
thisArg (可選的) - 設(shè)置執(zhí)行 callback 函數(shù)時(shí),this 的值
以上示例 map 方法實(shí)現(xiàn):
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado']; var prefixedHeroes = heroes.map(name => 'Super_' + name);filter
在 ES 5 版本之前,對于 heroes 數(shù)組,我們想獲取名字中包含 m 字母的英雄,我們可以這樣實(shí)現(xiàn):
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 方法,方便地實(shí)現(xiàn)上面的功能。
filter 方法簽名:
var new_array = arr.filter(callback[, thisArg])參數(shù)說明:
callback - 用來測試數(shù)組的每個(gè)元素的函數(shù)。調(diào)用時(shí)使用參數(shù) (element, index, array)。返回true表示保留該元素(通過測試),false則不保留。
thisArg (可選的) - 設(shè)置執(zhí)行 callback 函數(shù)時(shí),this 的值
以上示例 filter 方法實(shí)現(xiàn):
var heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado']; var filterRe = /m/i; var filterHeroes = heroes.filter(name => filterRe.test(name));參考資源
函數(shù)式編程初探
30天精通RxJS(02) 函數(shù)式編程基本概念
總結(jié)
以上是生活随笔為你收集整理的RxJS Functional Programming的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: eclipse egit 报错 The
- 下一篇: 深入理解PHP异常和错误处理(6)PHP