函數(shù)式編程恩沽、純函數(shù)誊稚、高階函數(shù)翔始、閉包、柯里化函數(shù)里伯,偏應(yīng)用城瞎,組合和管道,函子疾瓮,Generator

>>函數(shù)式編程:

原則:小

f(x) = y[數(shù)學(xué)函數(shù)]
1脖镀、函數(shù)必須總是接受一個參數(shù);
2狼电、函數(shù)必須總時返回一個值蜒灰;
3弦蹂、函數(shù)應(yīng)該依據(jù)接收到的參數(shù)(例如x),而不是外部環(huán)境運(yùn)行强窖;
4凸椿、對于一個給定的x,只會輸出唯一一個y翅溺。

函數(shù)式編程是一種范式脑漫,我們能夠以此創(chuàng)建僅依賴輸入就可以完成自身邏輯的函數(shù)。這保證了當(dāng)函數(shù)被多次調(diào)用時任然
返回相同的結(jié)果咙崎。函數(shù)不會改變?nèi)魏瓮獠凯h(huán)境的變量优幸,這將產(chǎn)生可緩存的,可測試的代碼庫褪猛。

  • 引用透明性:所有的函數(shù)對于相同的輸入都將返回相同值

  • 替換模型:直接替換函數(shù)的結(jié)果(主要因?yàn)楹瘮?shù)的邏輯不依賴其他全局的變量)网杆,這使并發(fā)代碼和緩存成為可能。

函數(shù)式編程主張聲明式編程編寫抽象的代碼握爷。
(區(qū)分聲明式編程和命令式編程:for 和 forEach)

“如何”做的部分將被抽象到普通函數(shù)中(高階函數(shù))

函數(shù)式編程主張以抽象的方式創(chuàng)建函數(shù)跛璧,這些函數(shù)能夠在代碼的其他部分被重用。

抽象:把復(fù)雜的東西抽出來新啼,變成簡單的東西追城。


>>純函數(shù):

純函數(shù)是數(shù)學(xué)函數(shù)

純函數(shù)是對給定的輸入返回相同的輸出的函數(shù)
->產(chǎn)生可測試的代碼(純函數(shù)不應(yīng)改變?nèi)魏瓮獠凯h(huán)境的變量)
->合理的代碼:必須具有一個有意義的名稱(通過函數(shù)名能夠輕易地推理出該函數(shù)的作用)
=>包含純函數(shù)的代碼易于閱讀、理解和測試燥撞。

js不是一種純函數(shù)語言(因?yàn)榭梢圆挥脜?shù)傳入)

js支持函數(shù)作為參數(shù)座柱,以及將函數(shù)傳值給另一函數(shù)等特性(js將函數(shù)視為一等公民)


>>高階函數(shù):

高階函數(shù):接受另一個函數(shù)作為其參數(shù)的函數(shù)稱為高階函數(shù)。

// JS七種基本類型
{
  Number
  String
  Boolean
  Object
  Null
  Undefined
  Symbol
}

*在《JavaScript權(quán)威指南》中把function被看做是object基本數(shù)據(jù)類型的一種特殊對象物舒,另外《悟透JavaScript》和《JavaScript高級程序設(shè)計》也把函數(shù)視為對象色洞,而不是一種基本數(shù)據(jù)類型。

高階函數(shù)是接受函數(shù)作為參數(shù)并且/或者返回函數(shù)作為輸出的函數(shù)冠胯。

大多數(shù)高階函數(shù)與閉包一起使用火诸。


>>閉包

閉包與作用域相關(guān)聯(lián):

outer(){
     inner(){
    ...
  }
}

inner是閉包,inner能訪問自身作用域的變量荠察,也能訪問全局變量置蜀,也能訪問外部函數(shù)父級(outer)作用域的變量。

  • 閉包能訪問外部函數(shù)的變量悉盆,該屬性使閉包變量非常強(qiáng)大盯荤!
  • 閉包可以記住它的上下文。

>>柯里化函數(shù)(curry):

(高階函數(shù)和閉包構(gòu)成柯里化函數(shù))

把一個多參數(shù)函數(shù)轉(zhuǎn)換為一個嵌套的一元函數(shù)的過程焕盟;
注:curry函數(shù)應(yīng)用參數(shù)列表的順序是從左到最右

  • 兩元函數(shù)轉(zhuǎn)柯里化
let curry = (fn) => {
            return function (x) {
                return function (y) {
                    return fn(x,y)
                }
            }
        }

let add = function (x,y) {
            return x+y
        }

curry(add)(2)(3) // => 5
  • 多參數(shù)函數(shù)轉(zhuǎn)化一元函數(shù)的curry函數(shù)
let curryN = (fn) => {
            if( typeof fn !== 'function' ){
                throw Error('no function provided');
            }
            return function curriedFn (...args) {
                if(args.length < fn.length){
                    return function () {
                        return curriedFn.apply(null,args.concat([].slice.call(arguments)));
                    }
                }
                return fn.apply(null,args);
            }
        }

let add = function (x,y,z,o,p) {
            return x+y+z+o+p
        }

curry(add)(2)(3)(4)(5)(6) // => 20

應(yīng)用場景:

// 封裝調(diào)試console
const loggerHelper = (mode,initialMsg,errorMsg,lineNo) => {
  if(mode === 'DeBug'){
    console.debug(initialMsg,errorMsg+"at line:"+lineNo)
  }else if(mode === 'Log'){
    console.log(initialMsg,errorMsg+"at line:"+lineNo)
  }else {
    throw Error("Wrong mode")
  }
}
//  利用柯里化轉(zhuǎn)化
let debugLogger = curryN(loggerHelper)('Log')("DeBug at stats.js")
debugLogger('debug message',123) // => DeBug at stats.js debug messageat line:123

// 在數(shù)組內(nèi)容中查找數(shù)字
let match = curryN(function (expr,str) {
  return str.match(expr)

})

let hasNumber = match(/[0-9]+/)
        
let filter = curryN(function (fn,arr) {
  return arr.filter(fn)
})

let findNumbersInArray = filter(hasNumber)

findNumbersInArray(['is','number2']) // =>['number2']
// 求數(shù)組的平方
let map = function (x) {
  return x*x
}

let squareAll = curryN(function (fn,arr) {
  return arr.map(fn)
})

console.log(squareAll(map)([2,3,4]))

>>偏函數(shù)(partial):

產(chǎn)生背景:curry函數(shù)應(yīng)用參數(shù)列表的順序是從左到最右秋秤,但有時候我們需要顛倒某兩個參數(shù)的順序

const partial = function (fn,...partialArgs) {
            let args = partialArgs;
            return function (...fullArguments) {
                let arg = 0;
                for (let i=0; i<args.length && arg<fullArguments.length; i++){
                    if(args[i] === undefined){
                        args[i] = fullArguments[arg++]
                    }
                }
                return fn.apply(null,args);
            }
        }

應(yīng)用場景:

// 定時執(zhí)行
let delayTenMsg = partial(setTimeout,undefined,10)
delayTenMsg(()=>{
  console.log('執(zhí)行結(jié)束')
})

partial應(yīng)用于任何含有多個參數(shù)的函數(shù):

let obj = {
  foo: 'bar',
  bar: 'foo'
}
JSON.stringify(obj,null,2)

// 偏函數(shù)改造:
let prettyPrintJSON = partial(JSON.stringify,undefined,null,2)
prettyPrintJSON(obj) 

注:這偏函數(shù)實(shí)現(xiàn)中有一個小bug,只能是一次性的,因?yàn)槲覀兪怯胾ndefined替換partialArgs灼卢,而數(shù)組傳遞的是引用绍哎!因而當(dāng)你第二次傳參let obj2 = { foo: 'bar2', bar: 'foo2' }打印出來的值還是上一次的obj結(jié)果。

總結(jié):什么時候用curry鞋真,什么時候用partial蛇摸,歸結(jié)于使用場景,curry的傳參必須從左到右依次執(zhí)行灿巧,partial用于解決這種不能從左到右依次執(zhí)行的情況赶袄。

柯里化和偏應(yīng)用是函數(shù)式編程的兩種重要技術(shù),作為一名js程序員抠藕,應(yīng)該在代碼庫中選擇柯里化或偏應(yīng)用直一饿肺。


>>組合和管道:

函數(shù)式組合在函數(shù)式中被稱為組合。

Unix的理念:

  • 每個程序只做好一件事盾似。為了完成一項新的任務(wù)敬辣,重新構(gòu)建要好于在復(fù)雜的舊程序中添加新屬性;

  • 每個程序的輸出應(yīng)該是另一個尚未知的程序輸入

  • 管道( | )在兩個命令之間扮演了橋梁的角色

  • 基礎(chǔ)函數(shù)需要遵循如下規(guī)則:每一個基礎(chǔ)函數(shù)都需要接收一個參數(shù)并返回數(shù)據(jù)零院!

組合函數(shù)的真正優(yōu)勢在于:無須創(chuàng)建新的函數(shù)就可以通過基礎(chǔ)函數(shù)解決眼前的問題溉跃。

compose函數(shù)定義:

  • 兩元組合函數(shù):
const compose = (a,b)=> c =>a(b(c))

應(yīng)用:

let number = compose(Math.round,parseFloat)
number('6.63') // =>7
let splitIntoSpace = (str) => str.split(" ")
let count = arr => arr.length
let countWords = compose(count,splitIntoSpace)
countWords("hello i am your dad") // =>5
  • 多元組合函數(shù):
const composeN = (...fns) => value => {
  let _fns = fns.reverse()
  return _fns.reduce((acc,fn)=>fn(acc),value)
}

應(yīng)用:

let splitIntoSpace = (str) => str.split(" ")
let count = arr => arr.length
let addOrEven = num => num%2 == 1 ? 'even' : 'add' // 判斷奇數(shù)還是偶數(shù)
let countWords = composeN(addOrEven,count,splitIntoSpace)
composeN(addOrEven,count,splitIntoSpace) // => "even"

pipe函數(shù)定義:

與compose函數(shù)所作的事請相同,只不過交換了數(shù)據(jù)流的方向告抄;
pipe和compose在現(xiàn)實(shí)開發(fā)中應(yīng)該二選一撰茎,否則會讓開發(fā)者感到困擾。

const pipe = (...fns) => value => {
  return fns.reduce((acc,fn)=>fn(acc),value)
}

組合滿足結(jié)合律:
composeN(f, composeN(g,h)) = composeN(composeN(f, g),h)

identity函數(shù)定義:

用于定位組合函數(shù)的數(shù)據(jù)流中哪個出錯

const identity = (it) =>{
            console.log(it)
            return it
        }
let countWords2 = composeN(addOrEven,count,identity,splitIntoSpace) 
// => ["hello", "i", "am", "your", "dad"]
// => "even"

>>函子:

(錯誤處理)
函子打洼,它將用一種純函數(shù)式的方式幫助我們處理錯誤龄糊;
函子是一個普通對象(在其他語言中,可能是一個類)募疮,它實(shí)現(xiàn)了map函數(shù)炫惩,在遍歷每對象值得時候生成一個新對象。

const Container = function (value) {
  this.value = value
}

Container.of = function(val){
  return new Container(val)
}

// 函子實(shí)現(xiàn) map方法:
Container.prototype.map = function (fn) {
  return Container.of(fn(this.value))
}


Container.of({a:3}) // =>Container {value: {a:3}}
Container.of([1,2,3]) // =>Container {value: [1,2,3]}
Container.of(3).map(e=>e+1) // =>Container {value: 4}

支持鏈?zhǔn)讲僮鳎?/p>

Container.of(3).map(e=>e+1).map(e=>e*e) // =>16

換句話講:函子是一個實(shí)現(xiàn)了map契約的對象阿浓!

MayBe函子:
利用函數(shù)式編程技術(shù)處理錯誤或異常的問題他嚷;
任何層級的鏈?zhǔn)絤ap都會被調(diào)用(管你傳了null還undefined)該過程將連接到鏈條中的最后一個map函數(shù)被調(diào)用。

const MayBe = function (value) {
  this.value = value
}

MayBe.of = function(val){
  return new MayBe(val)
}

MayBe.prototype.isNothing = function () {
  return (this.value === null || this.value === undefined)
}

// 函子實(shí)現(xiàn)map方法:
MayBe.prototype.map = function (fn) {
  return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this.value))
}

// 應(yīng)用
MayBe.of('Roy is a man').map(e=>e.toUpperCase()).map(e=> "Mr. " + e) // MayBe {value: "Mr. ROY IS A MAN"}

拓展:ES5的類為什么不可以用箭頭函數(shù)芭毙,因?yàn)槠洳痪哂衃[constructor]]和prototype屬性

Either函子:
Either的出現(xiàn)是為了解決MayBe不能返回undefined或null值執(zhí)行失敗的狀態(tài)

const Nothing = function (value) {
  this.value = value
}

Nothing.of = function(val){
  return new Nothing(val)
}

Nothing.prototype.map = function (fn) {
  return this // 返回對象本身
}

const Some = function (value) {
  this.value = value
}

Some.of = function(val){
  return new Some(val)
}

Some.prototype.map = function (fn) {
  return Some.of(fn(this.value))
}

// Either定義
const Either = {
  Some: Some,
  Nothing: Nothing
}

(案例代碼見代碼清單8-14)

Pointed函子:
函子只是一個實(shí)現(xiàn)了map契約的接口
Pointed函子是一個函子的子集筋蓖,它具有實(shí)現(xiàn)了of契約的接口

ES6增加了Array.of,這使數(shù)組成為一個Pointed函子稿蹲!

Array.of("you are a pointed functor,too?") // =>["you are a pointed functor,too?"]

Monad函子:
含有chain的Pointed函子被稱為Monad函子扭勉。

  • join解決問題

通過join解決問題鹊奖,解決map深層次嵌套問題

為MayBe函子添加一個join方法:將嵌套的結(jié)構(gòu)展開為一個單一層級

MayBe.prototype.join = function () {
  return this.isNothing() ? MayBe.of(null) : this.value
}
// join方法雖很簡單苛聘,但卻能幫助我們打開嵌套的MayBe
let joinExample = MayBe.of(MayBe.of(5)) // => MayBe { value : MayBe { value : 5} }
joinExample.join() //  => MayBe { value : 5}
  • 實(shí)現(xiàn)chain
    我們總是要在map后調(diào)用join,下面把邏輯封裝在一個名為chain的方法中。
MayBe.prototype.chain = function (fn) {
  return this.map(fn).join
}

總結(jié):重復(fù)的map調(diào)用會導(dǎo)致嵌套問題设哗,chain能幫助扁平化MayBe數(shù)據(jù)


>>Generator

作用:用generator的next調(diào)用替換回調(diào)

// 這里的業(yè)務(wù)場景:先通過接口1獲取picturesJson數(shù)據(jù)唱捣,然后通過picturesJson數(shù)據(jù)的url字段,再次請求接口2网梢,最終獲得第一張圖的數(shù)據(jù)
function request(url) {
  httpGetAsync(url,function (reponse) {
    generator.next(reponse)
  })
}

function *main() {
  let picturesJson = yield request('https://www.xxx.com/pics/.json')
  let firstPictureData = yield  request(picturesJson.data.children[0].data.url + '.json')
  console.log(firstPictureData) // 最后成功打印結(jié)果
}

// 執(zhí)行
let generator = main()
generator.next()

通過Generator解決了地獄回調(diào)問題震缭。

思考總結(jié):Generator的應(yīng)用場景主要用于解決某個數(shù)據(jù)需要等到上一個異步請求結(jié)果,早期這種情況战虏,我們會選擇回調(diào)的方法解決拣宰,但是如果遇到多個回調(diào)嵌套的問題,就會造成代碼不好維護(hù)(地獄回調(diào))烦感,選擇Generator替代回調(diào)巡社,代碼會變得更加直觀更好維護(hù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末手趣,一起剝皮案震驚了整個濱河市晌该,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绿渣,老刑警劉巖朝群,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異中符,居然都是意外死亡姜胖,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門淀散,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谭期,“玉大人,你說我怎么就攤上這事吧凉∷沓觯” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵阀捅,是天一觀的道長胀瞪。 經(jīng)常有香客問我,道長饲鄙,這世上最難降的妖魔是什么凄诞? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮忍级,結(jié)果婚禮上帆谍,老公的妹妹穿的比我還像新娘。我一直安慰自己轴咱,他們只是感情好汛蝙,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布烈涮。 她就那樣靜靜地躺著,像睡著了一般窖剑。 火紅的嫁衣襯著肌膚如雪坚洽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天西土,我揣著相機(jī)與錄音讶舰,去河邊找鬼。 笑死需了,一個胖子當(dāng)著我的面吹牛跳昼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肋乍,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼庐舟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了住拭?” 一聲冷哼從身側(cè)響起挪略,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎滔岳,沒想到半個月后杠娱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谱煤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年摊求,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刘离。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡室叉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出硫惕,到底是詐尸還是另有隱情茧痕,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布恼除,位于F島的核電站踪旷,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏豁辉。R本人自食惡果不足惜令野,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望徽级。 院中可真熱鬧气破,春花似錦、人聲如沸餐抢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至朴下,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間苦蒿,已是汗流浹背殴胧。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留佩迟,地道東北人团滥。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像报强,于是被迫代替她去往敵國和親灸姊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容