JavaScript函數(shù)式編程
函數(shù)式編程的基礎(chǔ)是一等函數(shù)(函數(shù)在js中作為一等公民)、作用域(詞法作用域,動(dòng)態(tài)作用域)和閉包。
函數(shù)式編程的第一個(gè)概念是高等函數(shù):高等函數(shù)將函數(shù)作為參數(shù),或者將函數(shù)作為返回值。
高等函數(shù)是函數(shù)式編程的基礎(chǔ),幾乎隨處可見高等函數(shù)。
由函數(shù)構(gòu)建函數(shù)
首先討論函數(shù)的構(gòu)建。函數(shù)式編程通過高等函數(shù)和一等函數(shù)構(gòu)建,常用的方法有三種
柯里化
柯里化為每一個(gè)邏輯參數(shù)返回一個(gè)新函數(shù),分為手動(dòng)柯里化和自動(dòng)柯里化。
手動(dòng)柯里化
自己手寫函數(shù)的柯里化版本,有柯里化方向的選擇(向左還是向右)
function div(n ,d) {
return n / d;
}
function leftCurry(n) {
return function (d) {
return n / d;
}
}
function rightCurry(d) {
return function (n) {
return n / d;
}
}
但這樣很麻煩,每個(gè)函數(shù)都要提供一個(gè)柯里化版本
自動(dòng)柯里化
指我們通過一個(gè)curry函數(shù)生成一個(gè)普通函數(shù)的柯里化版本
比如我們可以寫這樣一個(gè)curry2函數(shù)做到手動(dòng)柯里化中的那種效果
function curry2(func) {
return function (first) {
return function (second) {
return func(first, second);
};
};
}
function div(n, d) {
return n / d;
}
var leftCurry = curry(div);
這樣實(shí)現(xiàn)的一個(gè)leftCurry跟手動(dòng)柯里化實(shí)現(xiàn)的是一模一樣的;但是自動(dòng)柯里化是用過curry函數(shù)和div函數(shù)構(gòu)建出leftCurry函數(shù)的;這樣做也限定了柯里化的方向;我們可以通過再編寫一個(gè)curry函數(shù)使用另外一種方向來解決這個(gè)問題。
再說一個(gè)自動(dòng)柯里化的用處。這個(gè)柯里化如下:
function curry(func) {
return function (args) {
return func(args);
}
}
直觀上看這個(gè)柯里化有什么用?為什么不直接使用func(args)呢?
這個(gè)柯里化的場景在使用這樣的語句時(shí)格外有用[11, 11, 11, 11].map(parseInt)時(shí)格外有用。這行代碼貌似會(huì)返回[11, 11, 11, 11],但實(shí)際上的返回結(jié)果是[11, NaN, 3, 4]。
這是為什么呢?我們看了map的源碼就能知道,map接收的函數(shù),實(shí)際上是一個(gè)iteratee(item, index, array),也就是說,這行代碼實(shí)際運(yùn)行的是[parseInt(11, 0, array) parseInt(11, 1, array), parseInt(11, 2, array), parseInt(11, 3, array)]。
為了避免給這個(gè)iteratee傳入過多的參數(shù),我們可以通過柯里化返回一個(gè)只接受一個(gè)參數(shù)的函數(shù)
[11, 11, 11, 11].parseInt(curry(parseInt)),這樣我們就能得到理想的結(jié)果[11, 11, 11, 11].
柯里化的缺點(diǎn)
柯里化明顯只適合于有限參數(shù)的函數(shù);如果函數(shù)的參數(shù)過多(當(dāng)然我們杜絕設(shè)計(jì)這樣的函數(shù))或者函數(shù)的參數(shù)未定,那就不適合用柯里化來構(gòu)建函數(shù)。這時(shí)就更適合用partial。
partial
bind的實(shí)現(xiàn)其實(shí)就有partial的寫法;因?yàn)?code>bind在傳遞上下文的時(shí)候,也是可以傳部分參數(shù)的。
Function.prototype.bind = function (context) {
context = Obejct(context) || window;
// 保存一部分參數(shù)
var args = [].slice.call(arguments, 1);
// ...其他操作
return function () {
// 補(bǔ)全參數(shù)
args = args.concat([].slice.call(arguments));
}
}
compose
compose就是拼接函數(shù),返回一系列函數(shù)組合后的復(fù)合函數(shù),好比在數(shù)學(xué)里, 把函數(shù) f(), g(), 和 h() 組合起來可以得到復(fù)合函數(shù) f(g(h()))。
遞歸
- 遞歸最有名的應(yīng)用就是深克隆
- 遞歸操作都應(yīng)該被封裝
函數(shù)式編程的組成
這里有兩個(gè)React講到的概念:純函數(shù),不變性。
純函數(shù)
什么是純函數(shù)?
- 返回結(jié)果只由參數(shù)決定,不受外部數(shù)據(jù)的影響
- 不改變外部狀態(tài)
- 不受
Math.random,Date.now影響,沒有this,沒有全局變量
純函數(shù)便于函數(shù)的組成,有助于消除函數(shù)組合出來新的行為正確與否的擔(dān)憂:純函數(shù)的組合依舊是純函數(shù)。
不變性
js中的String類是最適合說不變性的。字符串類型的變量在調(diào)用方法后,返回調(diào)用后的值,但字符串本身是沒有被改變的。
鏈?zhǔn)秸{(diào)用
鏈?zhǔn)秸{(diào)用看underscore中_.chain的源碼就好。