日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > javascript >内容正文

javascript

JavaScript函数式编程入门经典

發(fā)布時(shí)間:2025/3/17 javascript 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JavaScript函数式编程入门经典 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一個(gè)持續(xù)更新的github筆記,鏈接地址:Front-End-Basics,可以watch,也可以star。

此篇文章的地址:JavaScript函數(shù)式編程入門(mén)經(jīng)典


正文開(kāi)始



什么是函數(shù)式編程?為何它重要?

數(shù)學(xué)中的函數(shù)

f(x) = y // 一個(gè)函數(shù)f,以x為參數(shù),并返回輸出y 復(fù)制代碼

關(guān)鍵點(diǎn):

  • 函數(shù)必須總是接受一個(gè)參數(shù)
  • 函數(shù)必須總是返回一個(gè)值
  • 函數(shù)應(yīng)該依據(jù)接收到的參數(shù)(例如x)而不是外部環(huán)境運(yùn)行
  • 對(duì)于一個(gè)給定的x,只會(huì)輸出唯一的一個(gè)y

函數(shù)式編程技術(shù)主要基于數(shù)學(xué)函數(shù)和它的思想,所以要理解函數(shù)式編程,先了解數(shù)學(xué)函數(shù)是有必要的。

函數(shù)式編程的定義

函數(shù)是一段可以通過(guò)其名稱(chēng)被調(diào)用的代碼。它可以接受參數(shù),并返回值。

與面向?qū)ο缶幊?#xff08;Object-oriented programming)和過(guò)程式編程(Procedural programming)一樣,函數(shù)式編程(Functional programming)也是一種編程范式。我們能夠以此創(chuàng)建僅依賴(lài)輸入就可以完成自身邏輯的函數(shù)。這保證了當(dāng)函數(shù)被多次調(diào)用時(shí)仍然返回相同的結(jié)果(引用透明性)。函數(shù)不會(huì)改變?nèi)魏瓮獠凯h(huán)境的變量,這將產(chǎn)生可緩存的,可測(cè)試的代碼庫(kù)。

函數(shù)式編程具有以下特征

1、引用透明性

所有的函數(shù)對(duì)于相同的輸入都將返回相同的值,函數(shù)的這一屬性被稱(chēng)為引用透明性(Referential Transparency)

// 引用透明的例子,函數(shù)identity無(wú)論輸入什么,都會(huì)原封不動(dòng)的返回 var identity = (i) => {return i} 復(fù)制代碼
替換模型

把一個(gè)引用透明的函數(shù)用于其他函數(shù)調(diào)用之間。

sum(4,5) + identity(1)

根據(jù)引用透明的定義,我們可以把上面的語(yǔ)句換成:

sum(4,5) + 1

該過(guò)程被稱(chēng)為替換模型(Substitution Model),因?yàn)楹瘮?shù)的邏輯不依賴(lài)其他全局變量,你可以直接替換函數(shù)的結(jié)果,這與它的值是一樣的。所以,這使得并發(fā)代碼緩存成為可能。

并發(fā)代碼: 并發(fā)運(yùn)行的時(shí)候,如果依賴(lài)了全局?jǐn)?shù)據(jù),要保證數(shù)據(jù)一致,必須同步,而且必要時(shí)需要鎖機(jī)制。遵循引用透明的函數(shù)只依賴(lài)參數(shù)的輸入,所以可以自由的運(yùn)行。

緩存: 由于函數(shù)會(huì)為給定的輸入返回相同的值,實(shí)際上我們就能緩存它了。比如實(shí)現(xiàn)一個(gè)計(jì)算給定數(shù)值的階乘的函數(shù),我們就可以把每次階乘的結(jié)果緩存下來(lái),下一次直接用,就不用計(jì)算了。比如第一次輸入5,結(jié)果是120,第二次輸入5,我們知道結(jié)果必然是120,所以就可以返回已緩存的值,而不必再計(jì)算一次。

2、聲明式和抽象

函數(shù)式編程主張聲明式編程和編寫(xiě)抽象的代碼。

比較命令式和聲明式
// 有一個(gè)數(shù)組,要遍歷它并把它打印到控制臺(tái)/*命令式*/ var array = [1,2,3] for(var i = 0; i < array.length; i++) console(array[i]) // 打印 1,2,3// 命令式編程中,我們精確的告訴程序應(yīng)該“如何”做:獲取數(shù)組的長(zhǎng)度,通過(guò)數(shù)組的長(zhǎng)度循環(huán)數(shù)組,在每一次循環(huán)中用索引獲取每一個(gè)數(shù)組元素,然后打印出來(lái)。 // 但是我們的任務(wù)只是打印出數(shù)組的元素。并不是要告訴編譯器要如何實(shí)現(xiàn)一個(gè)遍歷。/*聲明式*/ var array = [1,2,3] array.forEach((element) => console.log(element)) // 打印 1,2,3// 我們使用了一個(gè)處理“如何”做的抽象函數(shù),然后我們就能只關(guān)心做“什么”了 復(fù)制代碼
函數(shù)式編程主張以抽象的方式創(chuàng)建函數(shù),例如上文的forEach,這些函數(shù)能夠在代碼的其他部分被重用。

3、純函數(shù)

大多數(shù)函數(shù)式編程的好處來(lái)自于編寫(xiě)純函數(shù),純函數(shù)是對(duì)給定的輸入返回相同的輸出的函數(shù),并且純函數(shù)不應(yīng)依賴(lài)任何外部變量,也不應(yīng)改變?nèi)魏瓮獠孔兞俊?/p>

純函數(shù)的好處
  • 純函數(shù)產(chǎn)生容易測(cè)試的代碼
  • 純函數(shù)容易寫(xiě)出合理的代碼
  • 純函數(shù)容易寫(xiě)出并發(fā)代碼 純函數(shù)總是允許我們并發(fā)的執(zhí)行代碼。因?yàn)榧兒瘮?shù)不會(huì)改變它的環(huán)境,這意味著我們根本不需要擔(dān)心同步問(wèn)題。
  • 純函數(shù)的輸出結(jié)果可緩存 既然純函數(shù)總是為給定的輸入返回相同的輸出,那么我們就能夠緩存函數(shù)的輸出。
  • 高階函數(shù)

    數(shù)據(jù)和數(shù)據(jù)類(lèi)型

    程序作用于數(shù)據(jù),數(shù)據(jù)對(duì)于程序的執(zhí)行很重要。每種編程語(yǔ)言都有數(shù)據(jù)類(lèi)型。這些數(shù)據(jù)類(lèi)型能夠存儲(chǔ)數(shù)據(jù)并允許程序作用其中。

    JavaScript中函數(shù)是一等公民(First Class Citizens)

    **當(dāng)一門(mén)語(yǔ)言允許函數(shù)作為任何其他數(shù)據(jù)類(lèi)型使用時(shí),函數(shù)被稱(chēng)為一等公民。**也就是說(shuō)函數(shù)可被賦值給變量,作為參數(shù)傳遞,也可被其他函數(shù)返回。

    函數(shù)作為JavaScript的一種數(shù)據(jù)類(lèi)型,由于函數(shù)是類(lèi)似String的數(shù)據(jù)類(lèi)型,所以我們能把函數(shù)存入一個(gè)變量,能夠作為函數(shù)的參數(shù)進(jìn)行傳遞。所以JavaScript中函數(shù)是一等公民。

    高階函數(shù)的定義

    接受另一個(gè)函數(shù)作為其參數(shù)的函數(shù)稱(chēng)為高階函數(shù)(Higher-Order-Function),或者說(shuō)高階函數(shù)是接受函數(shù)作為參數(shù)并且/或者返回函數(shù)作為輸出的函數(shù)。

    抽象和高階函數(shù)

    一般而言,高階函數(shù)通常用于抽象通用的問(wèn)題,換句話(huà)說(shuō),高階函數(shù)就是定義抽象。

    抽象 : 在軟件工程和計(jì)算機(jī)科學(xué)中,抽象是一種管理計(jì)算機(jī)系統(tǒng)復(fù)雜性的技術(shù)。 通過(guò)建立一個(gè)人與系統(tǒng)進(jìn)行交互的復(fù)雜程度,把更復(fù)雜的細(xì)節(jié)抑制在當(dāng)前水平之下。簡(jiǎn)言之,抽象讓我們專(zhuān)注于預(yù)定的目標(biāo)而無(wú)須關(guān)心底層的系統(tǒng)概念。

    例如:你在編寫(xiě)一個(gè)涉及數(shù)值操作的代碼,你不會(huì)對(duì)底層硬件的數(shù)字表現(xiàn)方式到底是16位還是32位整數(shù)有很深的了解,包括這些細(xì)節(jié)在哪里屏蔽。因?yàn)樗鼈儽怀橄蟪鰜?lái)了,只留下了簡(jiǎn)單的數(shù)字給我們使用。

    // 用forEach抽象出遍歷數(shù)組的操作 const forEach = (array,fn) => {let i;for(i=0;i<array.length;i++) {fn(array[i])} }// 用戶(hù)不需要理解forEach是如何實(shí)現(xiàn)遍歷的,如此問(wèn)題就被抽象出來(lái)了。 //例如,想要打印出數(shù)組的每一項(xiàng) let array = [1,2,3] forEach(array,(data) => console.log(data)) 復(fù)制代碼

    閉包和高階函數(shù)

    什么是閉包?簡(jiǎn)言之,**閉包就是一個(gè)內(nèi)部函數(shù)。**什么是內(nèi)部函數(shù)?就是在另一個(gè)函數(shù)內(nèi)部的函數(shù)。

    閉包的強(qiáng)大之處在于它對(duì)作用域鏈(或作用域?qū)蛹?jí))的訪問(wèn)。從技術(shù)上講,閉包有3個(gè)可訪問(wèn)的作用域。

    (1) 在它自身聲明之內(nèi)聲明的變量

    (2) 對(duì)全局變量的訪問(wèn)

    (3) 對(duì)外部函數(shù)變量的訪問(wèn)(關(guān)鍵點(diǎn))

    實(shí)例一:假設(shè)你再遍歷一個(gè)來(lái)自服務(wù)器的數(shù)組,并發(fā)現(xiàn)數(shù)據(jù)錯(cuò)了。你想調(diào)試一下,看看數(shù)組里面究竟包含了什么。不要用命令式的方法,要用函數(shù)式的方法來(lái)實(shí)現(xiàn)。這里就需要一個(gè) tap 函數(shù)。

    const tap = (value) => {return (fn) => {typeof fn === 'function' && fn(value)console.log(value)} } // 沒(méi)有調(diào)試之前 forEach(array, data => {console.log(data + data) })// 在 forEach 中使用 tap 調(diào)試 forEach(array, data => {tap(data)(() => {console.log(data + data)}) }) 復(fù)制代碼

    完成一個(gè)簡(jiǎn)單的reduce函數(shù)

    const reduce = (array,fn,initialValue) => {let accumulator;if(initialValue != undefined)accumulator = initialValueelseaccumulator = array[0]if(initialValue === undefined)for(let i = 1; i < array.length; i++)accumulator = fn(accumulator, array[i])elsefor(let value of array)accumulator = fn(accumulator,value)return accumulator }console.log(reduce([1,2,3], (accumulator,value) => accumulator + value)) // 打印出6 復(fù)制代碼

    柯里化與偏應(yīng)用

    一些概念

    一元函數(shù)

    只接受一個(gè)參數(shù)的函數(shù)稱(chēng)為一元(unary)函數(shù)。

    二元函數(shù)

    只接受兩個(gè)參數(shù)的函數(shù)稱(chēng)為二元(binary)函數(shù)。

    變參函數(shù)

    變參函數(shù)是接受可變數(shù)量的函數(shù)。

    柯里化

    柯里化是把一個(gè)多參數(shù)函數(shù)轉(zhuǎn)換為一個(gè)嵌套的一元函數(shù)的過(guò)程。

    例如

    // 一個(gè)多參數(shù)函數(shù) const add = (x,y) => x + y; add(2,3)// 一個(gè)嵌套的一元函數(shù) const addCurried = x => y => x + y; addCurried(2)(3)// 然后我們寫(xiě)一個(gè)高階函數(shù),把 add 轉(zhuǎn)換成 addCurried 的形式。 const curry = (binaryFn) => {return function (firstArg) {return function (secondArg) {return binaryFn(firstArg,secondArg)}} } let autoCurriedAdd = carry(add) autoCurriedAdd(2)(3) 復(fù)制代碼

    上面只是簡(jiǎn)單實(shí)現(xiàn)了一個(gè)二元函數(shù)的柯里化,下面我們要實(shí)現(xiàn)一個(gè)更多參數(shù)的函數(shù)的柯里化。

    const curry = (fn) => {if (typeof fn !== 'function') {throw Error('No function provided')}return function curriedFn (...args) {// 判斷當(dāng)前接受的參數(shù)是不是小于進(jìn)行柯里化的函數(shù)的參數(shù)個(gè)數(shù)if(args.length < fn.length) {// 如果小于的話(huà)就返回一個(gè)函數(shù)再去接收剩下的參數(shù)return function (...argsOther) {return curriedFn.apply(null, args.concat(argsOther))}}else {return fn.apply(null,args)}} }const multiply = (x,y,z) => x * y * z;console.log(curry(multiply)(2)(3)(4)) 復(fù)制代碼

    柯里化的應(yīng)用實(shí)例:從數(shù)組中找出含有數(shù)字的元素

    let match = curry(function (expr,str) {return str.match(expr) }) let hasNumber = match(/[0-9]+/)let initFilter = curry(function (fn,array) {return array.filter(fn) })let findNumberInArray = initFilter(hasNumber) console.log(findNumberInArray(['aaa', 'bb2', '33c', 'ddd', ])) // 打印 [ 'bb2', '33c' ] 復(fù)制代碼

    偏應(yīng)用

    我們上面設(shè)計(jì)的柯里化函數(shù)總是在最后接受一個(gè)數(shù)組,這使得它能接受的參數(shù)列表只能是從最左到最右。

    但是有時(shí)候,我們不能按照從左到右的這樣嚴(yán)格傳入?yún)?shù),或者只是想部分地應(yīng)用函數(shù)參數(shù)。這里我們就需要用到偏應(yīng)用這個(gè)概念,它允許開(kāi)發(fā)者部分地應(yīng)用函數(shù)參數(shù)。

    const partial = function (fn, ...partialArgs) {return function (...fullArguments) {let args = partialArgslet 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)} } 復(fù)制代碼

    偏應(yīng)用的示例:

    // 打印某個(gè)格式化的JSON let prettyPrintJson = partial(JSON.stringify,undefined,null,2) console.log(prettyPrintJson({name:'fangxu',gender:'male'}))// 打印出 {"name": "fangxu","gender": "male" } 復(fù)制代碼

    組合與管道

    Unix的理念

  • 每個(gè)程序只做好一件事情,為了完成一項(xiàng)新的任務(wù),重新構(gòu)建要好于在復(fù)雜的舊程序中添加新“屬性”。
  • 每個(gè)程序的輸出應(yīng)該是另一個(gè)尚未可知的程序的輸入。
  • 每一個(gè)基礎(chǔ)函數(shù)都需要接受一個(gè)參數(shù)并返回?cái)?shù)據(jù)。
  • 組合(compose)

    const compose = (...fns) => {return (value) => reduce(fns.reverse(),(acc,fn) => fn(acc), value) } 復(fù)制代碼

    compose 組合的函數(shù),是按照傳入的順序從右到左調(diào)用的。所以傳入的 fns 要先 reverse 一下,然后我們用到了reduce ,reduce 的累加器初始值是 value ,然后會(huì)調(diào)用 (acc,fn) => fn(acc), 依次從 fns 數(shù)組中取出 fn ,將累加器的當(dāng)前值傳入 fn ,即把上一個(gè)函數(shù)的返回值傳遞到下一個(gè)函數(shù)的參數(shù)中。

    組合的實(shí)例:

    let splitIntoSpace = (str) => str.split(' ') let count = (array) => array.length const countWords = composeN(count, splitIntoSpace) console.log(countWords('make smaller or less in amount')) // 打印 6 復(fù)制代碼

    管道/序列

    compose 函數(shù)的數(shù)據(jù)流是從右往左的,最右側(cè)的先執(zhí)行。當(dāng)然,我們還可以讓最左側(cè)的函數(shù)先執(zhí)行,最右側(cè)的函數(shù)最后執(zhí)行。這種從左至右處理數(shù)據(jù)流的過(guò)程稱(chēng)為管道(pipeline)或序列(sequence)。

    // 跟compose的區(qū)別,只是沒(méi)有調(diào)用fns.reverse() const pipe = (...fns) => (value) => reduce(fns,(acc,fn) => fn(acc),value) 復(fù)制代碼

    函子

    什么是函子(Functor)?

    定義:函子是一個(gè)普通對(duì)象(在其它語(yǔ)言中,可能是一個(gè)類(lèi)),它實(shí)現(xiàn)了map函數(shù),在遍歷每個(gè)對(duì)象值的時(shí)候生成一個(gè)新對(duì)象。

    實(shí)現(xiàn)一個(gè)函子

    1、簡(jiǎn)言之,函子是一個(gè)持有值的容器。而且函子是一個(gè)普通對(duì)象。我們就可以創(chuàng)建一個(gè)容器(也就是對(duì)象),讓它能夠持有任何傳給它的值。

    const Container = function (value) {this.value = value }let testValue = new Container(1) // => Container {value:1} 復(fù)制代碼

    我們給 Container 增加一個(gè)靜態(tài)方法,它可以為我們?cè)趧?chuàng)建新的 Containers 時(shí)省略 new 關(guān)鍵字。

    Container.of = function (value) {return new Container(value) }// 現(xiàn)在我們就可以這樣來(lái)創(chuàng)建 Container.of(1) // => Container {value:1} 復(fù)制代碼

    2、函子需要實(shí)現(xiàn) map 方法,具體的實(shí)現(xiàn)是,map 函數(shù)從 Container 中取出值,傳入的函數(shù)把取出的值作為參數(shù)調(diào)用,并將結(jié)果放回 Container。

    為什么需要 map 函數(shù),我們上面實(shí)現(xiàn)的 Container 僅僅是持有了傳給它的值。但是持有值的行為幾乎沒(méi)有任何應(yīng)用場(chǎng)景,而 map 函數(shù)發(fā)揮的作用就是,允許我們使用當(dāng)前 Container 持有的值調(diào)用任何函數(shù)。

    Container.prototype.map = function (fn) {return Container.of(fn(this.value)) }// 然后我們實(shí)現(xiàn)一個(gè)數(shù)字的 double 操作 let double = (x) => x + x; Container.of(3).map(double) // => Container {value: 6} 復(fù)制代碼

    3、map返回了一傳入函數(shù)的執(zhí)行結(jié)果為值的 Container 實(shí)例,所以我們可以鏈?zhǔn)讲僮鳌?/p> Container.of(3).map(double).map(double).map(double) // => Container {value: 24} 復(fù)制代碼

    通過(guò)以上的實(shí)現(xiàn),我們可以發(fā)現(xiàn),函子就是一個(gè)實(shí)現(xiàn)了map契約的對(duì)象。函子是一個(gè)尋求契約的概念,該契約很簡(jiǎn)單,就是實(shí)現(xiàn) map 。根據(jù)實(shí)現(xiàn) map 函數(shù)的方式不同,會(huì)產(chǎn)生不同類(lèi)型的函子,如 MayBe 、 Either

    函子可以用來(lái)做什么?之前我們用tap函數(shù)來(lái)函數(shù)式的解決代碼報(bào)錯(cuò)的調(diào)試問(wèn)題,如何更加函數(shù)式的處理代碼中的問(wèn)題,那就需要用到下面我們說(shuō)的MayBe函子

    MayBe 函子

    讓我們先寫(xiě)一個(gè)upperCase函數(shù)來(lái)假設(shè)一種場(chǎng)景

    let value = 'string'; function upperCase(value) {// 為了避免報(bào)錯(cuò),我們得寫(xiě)這么一個(gè)判斷if(value != null || value != undefined)return value.toUpperCase() } upperCase(value) // => STRING 復(fù)制代碼

    如上面所示,我們代碼中經(jīng)常需要判斷一些null和undefined的情況。下面我們來(lái)看一下MayBe函子的實(shí)現(xiàn)。

    // MayBe 跟上面的 Container 很相似 export const MayBe = function (value) {this.value = value } MayBe.of = function (value) {return new MayBe(value) } // 多了一個(gè)isNothing MayBe.prototype.isNoting = function () {return this.value === null || this.value === undefined; } // 函子必定有 map,但是 map 的實(shí)現(xiàn)方式可能不同 MayBe.prototype.map = function(fn) {return this.isNoting()?MayBe.of(null):MayBe.of(fn(this.value)) }// MayBe應(yīng)用 let value = 'string'; MayBe.of(value).map(upperCase) // => MayBe { value: 'STRING' } let nullValue = null MayBe.of(nullValue).map(upperCase) // 不會(huì)報(bào)錯(cuò) MayBe { value: null } 復(fù)制代碼

    Either 函子

    MayBe.of("tony").map(() => undefined).map((x)f => "Mr. " + x) 復(fù)制代碼

    上面的代碼結(jié)果是 MyaBe {value: null},這只是一個(gè)簡(jiǎn)單的例子,我們可以想一下,如果代碼比較復(fù)雜,我們是不知道到底是哪一個(gè)分支在檢查 undefined 和 null 值時(shí)執(zhí)行失敗了。這時(shí)候我們就需要 Either 函子了,它能解決分支拓展問(wèn)題。

    const Nothing = function (value) {this.value = value; } Nothing.of = function (value) {return new Nothing(value) } Nothing.prototype.map = function (fn) {return this; } const Some = function (value) {this.value = value; } Some.of = function (value) {return new Some(value) } Some.prototype.map = function (fn) {return Some.of(fn(this.value)); }const Either = {Some,Nothing }復(fù)制代碼

    Pointed 函子

    函子只是一個(gè)實(shí)現(xiàn)了 map 契約的接口。Pointed 函子也是一個(gè)函子的子集,它具有實(shí)現(xiàn)了 of 契約的接口。 我們?cè)?MayBe 和 Either 中也實(shí)現(xiàn)了 of 方法,用來(lái)在創(chuàng)建 Container 時(shí)不使用 new 關(guān)鍵字。所以 MayBe 和 Either 都可稱(chēng)為 Pointed 函子。

    ES6 增加了 Array.of, 這使得數(shù)組成為了一個(gè) Pointed 函子。

    Monad 函子

    MayBe 函子很可能會(huì)出現(xiàn)嵌套,如果出現(xiàn)嵌套后,我們想要繼續(xù)操作真正的value是有困難的。必須深入到 MayBe 內(nèi)部進(jìn)行操作。

    let joinExample = MayBe.of(MayBe.of(5)); // => MayBe { value: MayBe { value: 5 } }// 這個(gè)時(shí)候我們想讓5加上4,需要深入 MayBe 函子內(nèi)部 joinExample.map((insideMayBe) => {return insideMayBe.map((value) => value + 4) }) // => MayBe { value: MayBe { value: 9 } } 復(fù)制代碼

    我們這時(shí)就可以實(shí)現(xiàn)一個(gè) join 方法來(lái)解決這個(gè)問(wèn)題。

    // 如果通過(guò) isNothing 的檢查,就返回自身的 value MayBe.prototype.join = function () {return this.isNoting()? MayBe.of(null) : this.value } 復(fù)制代碼let joinExample2 = MayBe.of(MayBe.of(5)); // => MayBe { value: MayBe { value: 5 } }// 這個(gè)時(shí)候我們想讓5加上4就很簡(jiǎn)單了。 joinExample2.join().map((value) => value + 4) // => MayBe { value: 9 } 復(fù)制代碼

    再延伸一下,我們擴(kuò)展一個(gè) chain 方法。

    MayBe.prototype.chain = function (fn) {return this.map(fn).join() } 復(fù)制代碼

    調(diào)用 chain 后就能把嵌套的 MayBe 展開(kāi)了。

    let joinExample3 = MayBe.of(MayBe.of(5)); // => MayBe { value: MayBe { value: 5 } }joinExample3.chain((insideMayBe) => {return insideMayBe.map((value) => value + 4) }) // => MayBe { value: 9 } 復(fù)制代碼

    Monad 其實(shí)就是一個(gè)含有 chain 方法的函子。只有of 和 map 的 MayBe 是一個(gè)函子,含有 chain 的函子是一個(gè) Monad。

    總結(jié)

    JavaScript是函數(shù)式編程語(yǔ)言嗎?

    函數(shù)式編程主張函數(shù)必須接受至少一個(gè)參數(shù)并返回一個(gè)值,但是JavaScript允許我們創(chuàng)建一個(gè)不接受參數(shù)并且實(shí)際上什么也不返回的函數(shù)。所以JavaScript不是一種純函數(shù)語(yǔ)言,更像是一種多范式的語(yǔ)言,不過(guò)它非常適合函數(shù)式編程范式。

    補(bǔ)充

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

    function generateGetNumber() {let numberKeeper = {}return function (number) {return numberKeeper.hasOwnProperty(number) ? number : numberKeeper[number] = number + number} } const getNumber = generateGetNumber() getNumber(1) getNumber(2) …… getNumber(9) getNumber(10)// 此時(shí)numberKeeper為: {1: 22: 43: 64: 85: 106: 127: 148: 169: 1810: 20 } 復(fù)制代碼

    現(xiàn)在我們規(guī)定,getNumber只接受1-10范圍的參數(shù),那么返回值肯定是 numberKeeper 中的某一個(gè) value 。據(jù)此我們分析一下 getNumber ,該函數(shù)接受一個(gè)輸入并為給定的范圍(此處范圍是10)映射輸出。輸入具有強(qiáng)制的、相應(yīng)的輸出,并且也不存在映射兩個(gè)輸出的輸入。

    下面我來(lái)再看一下數(shù)學(xué)函數(shù)的定義(維基百科)

    在數(shù)學(xué)中,函數(shù)是一種輸入集合和可允許的輸出集合之間的關(guān)系,具有如下屬性:每個(gè)輸入都精確地關(guān)聯(lián)一個(gè)輸出。函數(shù)的輸入稱(chēng)為參數(shù),輸出稱(chēng)為值。對(duì)于一個(gè)給定的函數(shù),所有被允許的輸入集合稱(chēng)為該函數(shù)的定義域,而被允許的輸出集合稱(chēng)為值域。

    根據(jù)我們對(duì)于 getNumber 的分析,對(duì)照數(shù)學(xué)函數(shù)的定義,會(huì)發(fā)現(xiàn)完全一致。我們上面的getNumber函數(shù)的定義域是1-10,值域是2,4,6,……18,20

    2、實(shí)例

    文中所有的概念對(duì)應(yīng)的實(shí)例可以在 github.com/qiqihaobenb… 獲取,可以打開(kāi)對(duì)應(yīng)的注釋來(lái)實(shí)際執(zhí)行一下。

    3、薦書(shū)

    《JavaScript ES6 函數(shù)式編程入門(mén)經(jīng)典》,強(qiáng)烈建議想入門(mén)函數(shù)式編程的同學(xué)看一下,書(shū)有點(diǎn)老,可以略過(guò)工具介紹之類(lèi)的,關(guān)鍵看其內(nèi)在的思想,最重要的是,這本書(shū)很薄,差不多跟一本漫畫(huà)書(shū)類(lèi)似。

    4、推薦文章(非引用文章)

  • 漫談 JS 函數(shù)式編程(一)
  • 從一道坑人的面試題說(shuō)函數(shù)式編程
  • 函數(shù)式編程入門(mén)教程
  • 函數(shù)式編程的一點(diǎn)實(shí)戰(zhàn)
  • 總結(jié)

    以上是生活随笔為你收集整理的JavaScript函数式编程入门经典的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。