JS輕量級(jí)函數(shù)式編程實(shí)現(xiàn)XDM二
前言
承接上一篇《XDM,JS如何函數(shù)式編程?看這就夠了?。ㄒ唬?/a>,我們知道了函數(shù)式編程的幾個(gè)基本概念。
這里作簡(jiǎn)要回顧:
- 函數(shù)式編程目的是為了數(shù)據(jù)流更加明顯,從而代碼更具可讀性;
- 函數(shù)需要一個(gè)或多個(gè)輸入(理想情況下只需一個(gè)?。┖鸵粋€(gè)輸出,輸入輸出是顯式的代碼將更好閱讀;
- 閉包是高階函數(shù)的基礎(chǔ);
- 警惕匿名函數(shù);
- 棄用 this 指向;
本篇將著重介紹第 2 點(diǎn)中函數(shù)的輸入,它是 JS 輕量函數(shù)式編程的基礎(chǔ)之基礎(chǔ),重要之重要?。?!
偏函數(shù)
傳參現(xiàn)狀
我們經(jīng)常會(huì)寫出這樣的代碼:
function ajax(url,data,callback) { // .. } function getPerson(data,cb) { ajax( "http://some.api/person", data, cb ); }
ajax 函數(shù)有三個(gè)入?yún)?,?getPerson 函數(shù)里調(diào)用,其中 url 已確定,data 和 cb 兩個(gè)參數(shù)則等待傳入。(因?yàn)楹芏鄷r(shí)候參數(shù)都不是在當(dāng)前能確定的,需要等待其它函數(shù)的操作后確定了再繼續(xù)傳入)
但是我們的原則是:入?yún)⒆罾硐氲那闆r下只需一個(gè)!
怎樣優(yōu)化,可以實(shí)現(xiàn)這一點(diǎn)呢?
我們或許可以在外層再套一個(gè)函數(shù)來(lái)進(jìn)一步確定傳參,比如:
function getCurrentUser(cb) { ...// 通過(guò)某些操作拿到 CURRENT_USER_ID getPerson( { user: CURRENT_USER_ID }, cb ); }
這樣,data 參數(shù)也已經(jīng)確定,cb 參數(shù)仍等待傳入;函數(shù) getCurrentUser 就只有一個(gè)入?yún)⒘耍?/p>
數(shù)據(jù)的傳遞路線是:
ajax(url,data,callback) => getPerson(data,cb) => getCurrentUser(cb)
這樣函數(shù)參數(shù)個(gè)數(shù)逐漸減少的過(guò)程就是偏應(yīng)用。
也可以說(shuō):getCurrentUser(cb) 是 getOrder(data,cb) 的偏函數(shù),getOrder(data,cb) 是 ajax(url,data,cb) 函數(shù)的偏函數(shù)。
設(shè)想下:
如果一個(gè)函數(shù)是這樣的:
function receiveMultiParam(a,b,c,......,x,y,z){ // .. }
我們難道還要像上面那樣手動(dòng)指定外層函數(shù)進(jìn)行逐層嵌套嗎?
顯示我們不會(huì)這么做!
封裝 partial
我們只需要封裝一個(gè) partial(..) 函數(shù):
function partial(fn,...presetArgs) { return function partiallyApplied(...laterArgs){ return fn( ...presetArgs, ...laterArgs ); }; }
它的基礎(chǔ)邏輯是:
var partial = (fn, ...presetArgs) => (...laterArgs) => fn( ...presetArgs, ...laterArgs );
把函數(shù)作為入?yún)?!還記得我們之前所說(shuō):
一個(gè)函數(shù)如果可以接受或返回一個(gè)甚至多個(gè)函數(shù),它被叫做高階函數(shù)。
我們借用 partial() 來(lái)實(shí)現(xiàn)上述舉例:
var getPerson = partial( ajax, "http://some.api/person" ); var getCurrentUser = partial( getPerson, { user: CURRENT_USER_ID } ); // 版本 1
以下函數(shù)內(nèi)部分析非常重要:
運(yùn)行機(jī)制
getPerson() 的內(nèi)部運(yùn)行機(jī)制是:
var getPerson = function partiallyApplied(...laterArgs) { return ajax( "http://some.api/person", ...laterArgs ); };
getCurrentUser() 的內(nèi)部運(yùn)行機(jī)制是:
var getCurrentUser = function outerPartiallyApplied(...outerLaterArgs) { var getPerson = function innerPartiallyApplied(...innerLaterArgs){ return ajax( "http://some.api/person", ...innerLaterArgs ); }; return getPerson( { user: CURRENT_USER_ID }, ...outerLaterArgs ); }
數(shù)據(jù)進(jìn)行了傳遞:
getCurrentUser(outerLaterArgs) => getPerson(innerLaterArgs) => ajax(...params)
我們通過(guò)這樣一層額外的函數(shù)包裝層,實(shí)現(xiàn)了更加強(qiáng)大的數(shù)據(jù)傳遞,
我們將需要減少參數(shù)輸入的函數(shù)傳入 partial()中作為第一個(gè)參數(shù),剩下的是 presetArgs,當(dāng)前已知幾個(gè),就可以寫幾個(gè)。還有不確定的入?yún)?laterArgs,可以在確定后繼續(xù)追加。
像這樣進(jìn)行額外的高階函數(shù)包裝層,是函數(shù)式編程的精髓所在!
“隨著本系列的繼續(xù)深入,我們將會(huì)把許多函數(shù)互相包裝起來(lái)。記住,這就是函數(shù)式編程!” —— 《JavaScript 輕量級(jí)函數(shù)式編程》
實(shí)際上,實(shí)現(xiàn) getCurrentUser() 還可以這樣寫:
// 版本 2 var getCurrentUser = partial( ajax, "http://some.api/person", { user: CURRENT_USER_ID } ); // 內(nèi)部實(shí)現(xiàn)機(jī)制 var getCurrentUser = function partiallyApplied(...laterArgs) { return ajax( "http://some.api/person", { user: CURRENT_USER_ID }, ...laterArgs ); };
但是版本 1 因?yàn)橹赜昧艘呀?jīng)定義好的函數(shù),所以它在表達(dá)上更清晰一些。它被認(rèn)為更加貼合函數(shù)式編程精神!
拓展 partial
我們?cè)倏纯?partial() 函數(shù)還可它用:
function partial(fn,...presetArgs) { return function partiallyApplied(...laterArgs){ return fn( ...presetArgs, ...laterArgs ); }; }
比如:將數(shù)組 [1,2,3,4,5] 每項(xiàng)都加 3,通常我們會(huì)這么做:
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(..) 函數(shù)里,通過(guò)偏應(yīng)用進(jìn)行處理后則能傳入;
實(shí)際上,partial() 函數(shù)還可以有很多變體:
回想我們之前調(diào)用 Ajax 函數(shù)的方式:ajax( url, data, cb )。如果要偏應(yīng)用 cb 而稍后再指定 data 和 url 參數(shù),我們應(yīng)該怎么做呢?
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 } );
柯里化
函數(shù)柯里化實(shí)際上是一種特殊的偏函數(shù)。
我們用 curry(..) 函數(shù)來(lái)實(shí)現(xiàn)此前的 ajax(..) 例子,它會(huì)是這樣的:
var curriedAjax = curry( ajax ); var personFetcher = curriedAjax( "http://some.api/person" ); var getCurrentUser = personFetcher( { user: CURRENT_USER_ID } ); getCurrentUser( function foundUser(user){ /* .. */ } );
柯里化函數(shù):接收單一實(shí)參(實(shí)參個(gè)數(shù):1)并返回另一個(gè)接收下一個(gè)實(shí)參的函數(shù)。
它將一個(gè)函數(shù)從可調(diào)用的 f(a, b, c) 轉(zhuǎn)換為可調(diào)用的 f(a)(b)(c)。
實(shí)現(xiàn):
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 ); } }; })( [] ); }
階段小結(jié)
我們?yōu)槭裁匆绱酥厝フ?ldquo;偏函數(shù)”(partial(sum,1,2)(3))或“柯里化”(sum(1)(2)(3))呢?
第一,是顯而易見的,偏函數(shù)或柯里化,可以將“指定分離實(shí)參”的時(shí)機(jī)和地方獨(dú)立開來(lái);
第二,更有重要意義的是,當(dāng)函數(shù)只有一個(gè)形參時(shí),我們能夠比較容易地組合它們。這種單元函數(shù),便于進(jìn)行后續(xù)的組合函數(shù);
對(duì)函數(shù)進(jìn)行包裝,使其成為一個(gè)高階函數(shù)是函數(shù)式編程的精髓!
至此,有了“偏函數(shù)”這門武器大炮,我們將逐漸轟開 JS輕量級(jí)函數(shù)式編程的面紗 ~
以上就是JS輕量級(jí)函數(shù)式編程實(shí)現(xiàn)XDM二的詳細(xì)內(nèi)容,更多關(guān)于JS輕量級(jí)函數(shù)式編程XDM的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用HTTP?Referer實(shí)現(xiàn)圖片防盜圖文示例詳解
這篇文章主要為大家介紹了使用HTTP?Referer實(shí)現(xiàn)圖片防盜圖文示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08JavaScript單例模式能不能去實(shí)例只留單原理解析
這篇文章主要為大家介紹了JavaScript單例模式能不能去實(shí)例只留單原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12微信小程序 input輸入及動(dòng)態(tài)設(shè)置按鈕的實(shí)現(xiàn)
這篇文章主要介紹了微信小程序 input輸入及動(dòng)態(tài)設(shè)置按鈕的實(shí)現(xiàn)的相關(guān)資料,希望通過(guò)本文能幫助到大家,需要的朋友可以參考下2017-10-10Dom-api MutationObserver使用方法詳解
這篇文章主要為大家介紹了Dom-api MutationObserver使用方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11深入內(nèi)存原理談JS中變量存儲(chǔ)在堆中還是棧中
JavaScript中基本類型存儲(chǔ)在堆中還是棧中,百度一下有很多不同的答案,本篇文章就來(lái)給大家為此做個(gè)詳細(xì)的介紹,需要的朋友可以參考一下2021-09-09一文詳解typeScript的extends關(guān)鍵字
這篇文章主要為大家介紹了typeScript的extends關(guān)鍵字使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03動(dòng)態(tài)內(nèi)存分配導(dǎo)致影響Javascript性能的問(wèn)題
今天小編就為大家分享一篇關(guān)于動(dòng)態(tài)內(nèi)存分配導(dǎo)致影響Javascript性能的問(wèn)題,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12