函數(shù)編程式這個(gè)概念在最近幾年的開(kāi)發(fā)中似乎又變得流行起來(lái)。因此,筆者覺(jué)得有必要對(duì)這方面進(jìn)行一些學(xué)習(xí)和了解。本文就是筆者在閱讀完《: ES6函數(shù)式編程入門經(jīng)典》做的一些筆記,希望能理清一些函數(shù)編程式中的重要概念。
1.什么是函數(shù)編程式?
在《Javscript ES6函數(shù)式編程入門經(jīng)典》中,作者將函數(shù)編程式定義為:
函數(shù)編程式是一種編程范式,我們能夠以此創(chuàng)建僅依賴輸入就可以完成自身邏輯的函數(shù)。這保證了當(dāng)函數(shù)被多次調(diào)用時(shí)仍然返回相同的結(jié)果。
可能看了這段定義,大家對(duì)于函數(shù)編程式還是不太理解。這和我們平常的面對(duì)對(duì)象或者面向過(guò)程的編程到底有什么區(qū)別呢?舉一個(gè)簡(jiǎn)單的例子:現(xiàn)在有一個(gè)需求,讓我們?nèi)ケ闅v數(shù)組中的元素并將其打印到控制臺(tái)。大家可能會(huì)不假思索地寫出下面的代碼:
代碼清單 1-1
var arr = [1,2,3,4];
for(var i = 0; i< arr.length; i++) {
console.log(i);//打印出 1,2,3,4
}
1-1中的代碼風(fēng)格我們可以稱之為命令式編程,也就是說(shuō),告訴程序應(yīng)該怎么做。而在聲明式函數(shù)中,我們僅僅告訴程序做什么,而不是如何做。如何做的這部分將被抽象到普通函數(shù)中。下面的代碼就是聲明式編程。
代碼清單1-2
var arr = [1,2,3,4];
arr.forEach(item => console.log(item))
1-2中的代碼中forEach函數(shù)的功能就是遍歷,它是一個(gè)被抽象的函數(shù),具體的邏輯實(shí)現(xiàn)已經(jīng)被封裝在了函數(shù)中。如果還不明白。建議大家可以參考一下知乎上的這個(gè)問(wèn)題:什么是函數(shù)式編程思維,分析的比較清楚。
2.理解高階函數(shù)
在函數(shù)編程式中,高階函數(shù)是一個(gè)基礎(chǔ)的概念,我們必須去理解它。那么什么是高階函數(shù)呢?定義如下:
高階函數(shù)是接受函數(shù)作為參數(shù)并且/或者返回函數(shù)作為輸出的函數(shù)。
簡(jiǎn)單的來(lái)說(shuō),高階函數(shù)可以將函數(shù)作為參數(shù),并且也可以把函數(shù)作為返回的對(duì)象。相關(guān)代碼如下:
代碼清單2-1
//將函數(shù)作為傳輸 并且把函數(shù)作為返回對(duì)象
const demo = fn => fn;
//更具體的一個(gè)例子
const forEach = (arr,fn) => {
for(var i = 0; i < arr.length; i++ ){
fn(arr[i])
}
}
3. 理解柯里化
談到函數(shù)編程式就離不開(kāi)柯里化,那么到底什么是柯里化呢? 定義如下:
柯里化是將一個(gè)多元函數(shù)轉(zhuǎn)換為一個(gè)嵌套的一元函數(shù)的過(guò)程。
是不是不太明白呢? 我們可以通過(guò)下面的例子來(lái)了解一下。假設(shè)我們有一個(gè)加法函數(shù),代碼如下:
const add = (x, y)=> x+y
首先,它有2個(gè)參數(shù),所以它是一個(gè)二元函數(shù)。按照柯里化的定義,我們需要將這個(gè)二元函數(shù)轉(zhuǎn)化為一個(gè)嵌套的一元函數(shù)。那么到底怎么轉(zhuǎn)呢,我們將會(huì)用到閉包,代碼如下(如果對(duì)此概念不太熟悉,建議可以查詢一些相關(guān)資料。)
const addCurried = x => y => x+y
上述代碼將一個(gè)二元函數(shù)轉(zhuǎn)化為了一個(gè)嵌套的一元函數(shù),是不是完全符合我們對(duì)柯里化的理解!最后我們可以總結(jié)一下這個(gè)柯里化的函數(shù)(下面的代碼只針對(duì)二元轉(zhuǎn)一元),代碼如下:
const curried() = fn => firstArg => secondArg => fn(firstArg, secondArg)
4. 理解組合
組合概念也是函數(shù)編程式中一個(gè)非常重要的概念。簡(jiǎn)單地來(lái)說(shuō),組合就是把2個(gè)小的單一功能的函數(shù)拼接成一個(gè)具有實(shí)際功能的函數(shù)。這里面包含了Unix中的兩個(gè)非常重要的思想:?jiǎn)我宦毮芎凸艿馈?/p>
- 單一職能: 每個(gè)程序只做好一件事情。
- 管道: 每一個(gè)程序的輸出應(yīng)該是另一個(gè)未知程序的輸入。
我們可以先看一個(gè)簡(jiǎn)單的例子:有一個(gè)需求,要求計(jì)算字符串中英文單詞的個(gè)數(shù)。按照Unix的思想,我們可以將這個(gè)需求分為2個(gè)小的函數(shù),并且將這兩個(gè)函數(shù)使用compose組合,就達(dá)到了我們想要的目的。相關(guān)代碼如下:
let splitIntoWords = str => str.split(' ') // 將數(shù)組以空格進(jìn)行分開(kāi) 組裝成數(shù)組
let count = arr => arr.length //計(jì)算數(shù)組的長(zhǎng)度
compose(count,splitIntoWords); //將這2個(gè)函數(shù)組合起來(lái) 完成了需求的執(zhí)行
相關(guān)的compose代碼如下:
const compose = (a,b) => b(a(c))
上述的代碼只是簡(jiǎn)單地將2個(gè)函數(shù)以從右到左的順序組合起來(lái)。大家可以思考一下如何將多個(gè)函數(shù)組合起來(lái)。
5.理解函子
函子這個(gè)概念在初次接觸的時(shí)候不太好理解,在筆者看來(lái),函子更像是騎了一個(gè)過(guò)濾器的作用。相關(guān)定義如下:
函子是一個(gè)普通對(duì)象,它實(shí)現(xiàn)了map函數(shù),在遍歷每個(gè)對(duì)象值的時(shí)候生成一個(gè)新對(duì)象。
是不是有點(diǎn)抽象,我們可以通過(guò)具體代碼來(lái)理解一下:
const Container = function(value){
this.value = value;
};
Container.of = function(value){
return new Container(value);
}
Container.prototype.map = function(fn){
return Container.of(fn(this.value));
}
簡(jiǎn)單地來(lái)說(shuō),函子就是一個(gè)實(shí)現(xiàn)了map契約的對(duì)象。它可以幫助我們?nèi)プ鰧?duì)值類型的過(guò)濾處理異常錯(cuò)誤。
以上就是筆者關(guān)于javascript編程式的一些筆記。寫完這邊文章才發(fā)現(xiàn)自己關(guān)于函數(shù)編程式的理解還有很多不太清楚的地方。所以筆者也會(huì)繼續(xù)強(qiáng)化對(duì)于函數(shù)編程式的學(xué)習(xí)。