承接上一篇《XDM,JS如何函數式編程?看這就夠了!(一)》,咱們知道了函數式編程的幾個基本概念。ajax
這裏做簡要回顧:編程
- 函數式編程目的是爲了數據流更加明顯,從而代碼更具可讀性;
- 函數須要一個或多個輸入(理想狀況下只需一個!)和一個輸出,輸入輸出是顯式的代碼將更好閱讀;
- 閉包是高階函數的基礎;
- 警戒匿名函數;
- 棄用 this 指向;
本篇將着重介紹第 2 點中函數的輸入,它是 JS 輕量函數式編程的基礎之基礎,重要之重要!!!api
咱們常常會寫出這樣的代碼:數組
function ajax(url,data,callback) {
// ..
}
function getPerson(data,cb) {
ajax( "http://some.api/person", data, cb );
}
複製代碼
ajax
函數有三個入參,在 getPerson
函數裏調用,其中 url 已肯定,data 和 cb 兩個參數則等待傳入。(由於不少時候參數都不是在當前能肯定的,須要等待其它函數的操做後肯定了再繼續傳入)markdown
可是咱們的原則是:入參最理想的狀況下只需一個!閉包
怎樣優化,能夠實現這一點呢?函數式編程
咱們或許能夠在外層再套一個函數來進一步肯定傳參,好比:函數
function getCurrentUser(cb) {
...// 經過某些操做拿到 CURRENT_USER_ID
getPerson( { user: CURRENT_USER_ID }, cb );
}
複製代碼
這樣,data 參數也已經肯定,cb 參數仍等待傳入;函數 getCurrentUser
就只有一個入參了!post
數據的傳遞路線是:優化
ajax(url,data,callback) => getPerson(data,cb) => getCurrentUser(cb)
複製代碼
這樣函數參數個數逐漸減小的過程就是偏應用。
也能夠說:getCurrentUser(cb)
是 getOrder(data,cb)
的偏函數,getOrder(data,cb)
是 ajax(url,data,cb)
函數的偏函數。
設想下:
若是一個函數是這樣的:
function receiveMultiParam(a,b,c,......,x,y,z){
// ..
}
複製代碼
咱們難道還要像上面那樣手動指定外層函數進行逐層嵌套嗎?
顯示咱們不會這麼作!
咱們只須要封裝一個 partial(..)
函數:
function partial(fn,...presetArgs) {
return function partiallyApplied(...laterArgs){
return fn( ...presetArgs, ...laterArgs );
};
}
複製代碼
它的基礎邏輯是:
var partial =
(fn, ...presetArgs) =>
(...laterArgs) =>
fn( ...presetArgs, ...laterArgs );
複製代碼
把函數做爲入參!還記得咱們以前所說:
一個函數若是能夠接受或返回一個甚至多個函數,它被叫作高階函數。
咱們借用 partial()
來實現上述舉例:
var getPerson = partial( ajax, "http://some.api/person" );
var getCurrentUser = partial( getPerson, { user: CURRENT_USER_ID } ); // 版本 1
複製代碼
如下函數內部分析很是重要:
getPerson()
的內部運行機制是:
var getPerson = function partiallyApplied(...laterArgs) {
return ajax( "http://some.api/person", ...laterArgs );
};
複製代碼
getCurrentUser()
的內部運行機制是:
var getCurrentUser = function outerPartiallyApplied(...outerLaterArgs) {
var getPerson = function innerPartiallyApplied(...innerLaterArgs){
return ajax( "http://some.api/person", ...innerLaterArgs );
};
return getPerson( { user: CURRENT_USER_ID }, ...outerLaterArgs );
}
複製代碼
數據進行了傳遞:
getCurrentUser(outerLaterArgs) => getPerson(innerLaterArgs) => ajax(...params)
複製代碼
咱們經過這樣一層額外的函數包裝層,實現了更增強大的數據傳遞,
咱們將須要減小參數輸入的函數傳入 partial()
中做爲第一個參數,剩下的是 presetArgs
,當前已知幾個,就能夠寫幾個。還有不肯定的入參 laterArgs
,能夠在肯定後繼續追加。
像這樣進行額外的高階函數包裝層,是函數式編程的精髓所在!
「隨着本系列的繼續深刻,咱們將會把許多函數互相包裝起來。記住,這就是函數式編程!」 —— 《JavaScript 輕量級函數式編程》
實際上,實現 getCurrentUser()
還能夠這樣寫:
// 版本 2
var getCurrentUser = partial(
ajax,
"http://some.api/person",
{ user: CURRENT_USER_ID }
);
// 內部實現機制
var getCurrentUser = function partiallyApplied(...laterArgs) {
return ajax(
"http://some.api/person",
{ user: CURRENT_USER_ID },
...laterArgs
);
};
複製代碼
可是版本 1 由於重用了已經定義好的函數,因此它在表達上更清晰一些。它被認爲更加貼合函數式編程精神!
咱們再看看 partial()
函數還可它用:
function partial(fn,...presetArgs) {
return function partiallyApplied(...laterArgs){
return fn( ...presetArgs, ...laterArgs );
};
}
複製代碼
好比:將數組 [1,2,3,4,5] 每項都加 3,一般咱們會這麼作:
function add(x,y) {
return x + y
[1,2,3,4,5].map( function adder(val){
return add( 3, val );
} );
// [4,5,6,7,8]
複製代碼
藉助 partial()
:
[1,2,3,4,5].map( partial( add, 3 ) );
// [4,5,6,7,8]
複製代碼
add(..) 不能直接傳入 map(..) 函數裏,經過偏應用進行處理後則能傳入;
實際上,partial()
函數還能夠有不少變體:
回想咱們以前調用 Ajax 函數的方式:ajax( url, data, cb )。若是要偏應用 cb 而稍後再指定 data 和 url 參數,咱們應該怎麼作呢?
function reverseArgs(fn) {
return function argsReversed(...args){
return fn( ...args.reverse() );
};
}
function partialRight( fn, ...presetArgs ) {
return reverseArgs(
partial( reverseArgs( fn ), ...presetArgs.reverse() )
);
}
var cacheResult = partialRight( ajax, function onResult(obj){
cache[obj.id] = obj;
});
// 處理後:
cacheResult( "http://some.api/person", { user: CURRENT_USER_ID } );
複製代碼
函數柯里化其實是一種特殊的偏函數。
咱們用 curry(..) 函數來實現此前的 ajax(..) 例子,它會是這樣的:
var curriedAjax = curry( ajax );
var personFetcher = curriedAjax( "http://some.api/person" );
var getCurrentUser = personFetcher( { user: CURRENT_USER_ID } );
getCurrentUser( function foundUser(user){ /* .. */ } );
複製代碼
柯里化函數:接收單一實參(實參個數:1)並返回另外一個接收下一個實參的函數。
它將一個函數從可調用的 f(a, b, c) 轉換爲可調用的 f(a)(b)(c)。
實現:
function curry(fn,arity = fn.length) {
return (function nextCurried(prevArgs){
return function curried(nextArg){
var args = prevArgs.concat( [nextArg] );
if (args.length >= arity) {
return fn( ...args );
}
else {
return nextCurried( args );
}
};
})( [] );
}
複製代碼
咱們爲何要如此着重去談「偏函數」(partial(sum,1,2)(3))或「柯里化」(sum(1)(2)(3))呢?
第一,是顯而易見的,偏函數或柯里化,能夠將「指定分離實參」的時機和地方獨立開來;
第二,更有重要意義的是,當函數只有一個形參時,咱們可以比較容易地組合它們。這種單元函數,便於進行後續的組合函數;
對函數進行包裝,使其成爲一個高階函數是函數式編程的精髓!
至此,有了「偏函數」這門武器大炮,咱們將逐漸轟開 JS 輕量級函數式編程的面紗 ~
以上。
我是掘金安東尼,公衆號【掘金安東尼】,點贊👍關注👀,輸出暴露輸入!持續追蹤!