函數(shù)式編程 ( Functional Programming ) 是一種以函數(shù)為基礎(chǔ)的編程方式和代碼組織方式,能夠帶來更好的代碼調(diào)試及項(xiàng)目維護(hù)的優(yōu)勢。本篇主要結(jié)合筆者在實(shí)際項(xiàng)目開發(fā)中的一些應(yīng)用,簡要談?wù)労瘮?shù)式編程。
函數(shù)
在函數(shù)式編程中,任何代碼可以都是函數(shù),且要求具有返回值,如下示例
// 非函數(shù)式
var title = "Functional Programming";
var saying = "This is not";
console.log(saying + title); // => This is not Functional Programming
// 函數(shù)式
var say = title => "This is " + title;
var text = say("Functional Programming"); // => This is Functional Programming
純函數(shù)
純函數(shù)在這里指函數(shù)內(nèi)外間是“無”關(guān)聯(lián)的。主要有下面兩點(diǎn)
沒有副作用(side effect)不會(huì)涉及到外部變量的使用或修改
引用透明函數(shù)內(nèi)只會(huì)依賴傳入?yún)?shù),在任何時(shí)候?qū)瘮?shù)輸入相同的參數(shù)時(shí),總能輸出相同的結(jié)果
// 非純函數(shù)(函數(shù)內(nèi)依賴函數(shù)外的變量值)
var title = "Functional Programming";
var say = ()=> "This is not" + title; // <= 依賴了全局變量 title
// 純函數(shù)
var say = (title)=>"This is " + title; // <= 依賴了以參數(shù) title 傳入
say("Functional Programming");
不可變數(shù)據(jù)(immutable)
這里主要是指變量值的不可變。當(dāng)需要基于原變量值改變時(shí),可通過產(chǎn)生新的變量來確保原變量的不變性,如下
// 可變數(shù)據(jù)
var arr = ["Functional", "Programming"];
arr[0] = "Other"; // <= 修改了arr[0]的值
console.log(arr) // => ["Other", "Programming"] // 變量arr值已經(jīng)被修改
// 不可變數(shù)據(jù)
var arr = ["Functional", "Programming"];
// 得到新的變量,不修改了原來的值
var newArr = arr.map(item => {
if(item === "Functional"){
return "Other";
} else {
return item;
}
})
console.log(arr); // => ["Functional", "Programming"] 變量arr值不變
console.log(newArr); // => ["Other", "Programming"] 產(chǎn)生新的變量newArr
之所以使用這種不變值,除了更好的函數(shù)式編程外,還能夠維持線程安全可靠,落地在業(yè)務(wù)中,實(shí)際上也能讓代碼更加清晰。設(shè)想,如果你定義了一個(gè)變量A,A在其他地方被其他人修改了,這樣是不方便定位A的當(dāng)前值的。關(guān)于定義多個(gè)變量引發(fā)的內(nèi)存等問題,可以通過重用結(jié)構(gòu)或部分引用的方式來減輕,可參考 immutable.js
使用 map, reduce 等數(shù)據(jù)處理函數(shù)
強(qiáng)大的 JavaScript 有著越來越多的高能處理數(shù)據(jù)函數(shù),其中包含了 map、 reduce、 filter 等。
map 能夠?qū)υ瓟?shù)組中的值進(jìn)行逐個(gè)處理并產(chǎn)生新的數(shù)組,一個(gè)簡單例子
// map
var data = [1, 2, 3];
var squares = data.map( (item, index, array) => item * item );
console.log(squares); // => [1, 4, 9]
console.log(data);// => [1, 2, 3] data 還是那個(gè) data
reduce 能夠?qū)υ瓟?shù)組中的各個(gè)值進(jìn)行結(jié)合處理,來產(chǎn)生新的值,如下面例子中,previous 代表上一個(gè)結(jié)果值,current 代表當(dāng)前值,reduce 函數(shù)可以傳入第二個(gè)參數(shù)作為 previous 初始值,不傳時(shí)則 previous 初始值為數(shù)組中第一個(gè)值。
// reduce
var sum = [1, 2, 3].reduce( (previous, current, index, array) => previous + current );
console.log(sum); // => 6
函數(shù)柯里化 Currying
柯里化 是將多參函數(shù)轉(zhuǎn)換成一系列的單參函數(shù)。結(jié)合下面例子來說明下
// 一個(gè)多參函數(shù)
var add = (a, b) => a + b;
add(1, 2); // => 3
將上面的多參函數(shù)進(jìn)行柯里化,如下
// 柯里化函數(shù)
var add = a => b => a + b;
上面柯里化后的函數(shù)調(diào)用方式也有所轉(zhuǎn)變,第一次傳入一個(gè)參數(shù)返回了一個(gè)函數(shù),再傳入?yún)?shù)則完成整體的調(diào)用,這也是利用的閉包的特性
var add1 = add(1);
add1(2); // => 3
柯里化后的函數(shù),也可以應(yīng)用在生產(chǎn) “ 函數(shù) ” 上,如下示例
var say = title => type => title + " is " + type;
var sayFP = say("Functional Programming");
var sayOther = say("Other Programming");
sayFP("good"); // => Functional Programming is good
sayOther("good"); // => Other Programming is good
組合函數(shù) compose
顧名思義,組合函數(shù)是將多個(gè)函數(shù)進(jìn)行組合成一個(gè)函數(shù)。舉個(gè)例子
var compose = (fn1, fn2) => (arg) => fn1(fn2(arg));
var a = arg => arg + 'a';
var b = arg => arg + 'b';
var c = compose(a, b); // 將a,b函數(shù)進(jìn)行組合
c('c'); // => cba
上面示例中,當(dāng)調(diào)用組合函數(shù) c 時(shí),傳入的參數(shù)會(huì)經(jīng)過 b 函數(shù),接著將 b 函數(shù)的返回值作為 a 函數(shù)的參數(shù)值,從而輸出最終結(jié)果。組合函數(shù) c 就像管道一樣,將水流( 返回值 )流經(jīng)各個(gè)函數(shù)中進(jìn)行處理。
當(dāng)想要組合很多函數(shù)成一條很長很長的“管道”時(shí),那么顯然上面的 compose 函數(shù)已經(jīng)不夠用了。下面看看 redux 是怎么做這個(gè) compose 工具函數(shù)的。
// 源自: redux/src/compose.js
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
} else {
const last = funcs[funcs.length - 1]
const rest = funcs.slice(0, -1)
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}
}
代碼很簡潔,主要利用了遞歸方式和數(shù)組的 reduceRight 方法來處理,reduceRight 跟上邊提到的 reduce 方法功能是一樣的,不同的是 reduceRight 是從數(shù)組的末尾向前逐個(gè)處理。就這樣,想拼多長的就多長。
以上,便是筆者在項(xiàng)目實(shí)踐中應(yīng)用較多的函數(shù)式編程內(nèi)容,如有不妥,請(qǐng)斧正。
附: 一些可供學(xué)習(xí)函數(shù)式編程的內(nèi)容
Immutable.js (https://facebook.github.io/immutable-js/)
Underscore (http://underscorejs.org/)
Lodash (https://lodash.com/)
Ramda (http://ramdajs.com/)
Mori (http://swannodette.github.io/mori/)
Monads (http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html)
轉(zhuǎn)載自AlloyTeam:http://www.alloyteam.com/2016/09/talk-about-functional-programming/