函數(shù)式編程簡介

函數(shù)式編程無疑是目前的熱門技術(shù)話題之一意鲸,值得每個開發(fā)者認(rèn)真研究。iOS觉既、Android這幾年不約而同的都更換了編程語言昂羡,從Objective-C到Swift,從Java到Kotlin鸣皂,一個明顯的改變就是抓谴,語言不再是純粹面向?qū)ο蟮哪乎澹技尤雽瘮?shù)式編程的支持。

讀過《代碼大全》的朋友應(yīng)該對“軟件復(fù)雜度”一詞印象深刻癌压,“軟件復(fù)雜度”估計是全書出現(xiàn)頻率最高的詞了椎侠,也是技術(shù)發(fā)展的核心因變量。很多編程技巧措拇、思想都是為了控制和降低軟件復(fù)雜度我纪。

復(fù)雜度就是任何使軟件難以理解或修改的東西∝は牛— John Outerhout

回想一下被自己或同事的一大坨代碼折磨的可怕情景浅悉,馬上要發(fā)版上線了,突然發(fā)現(xiàn)有一個bug需要修改券犁,令人沮喪的是术健,代碼千纏百結(jié),動一發(fā)而牽全身粘衬,只能硬著頭皮碰運氣荞估。有什么解決辦法嗎?函數(shù)式編程或許有所幫助稚新。

函數(shù)式編程中的不可變性勘伺、純函數(shù)等概念幫助我們消除了函數(shù)的副作用,有效降低了代碼的復(fù)雜度褂删。

什么是函數(shù)式編程飞醉?

函數(shù)式編程是一種編程范式(構(gòu)建計算機(jī)程序結(jié)構(gòu)和元素的風(fēng)格),將計算視為函數(shù)的求值屯阀,避免狀態(tài)變化和可變數(shù)據(jù)缅帘。— 維基百科

一種聲明式編程范式难衰,因為編程是用表達(dá)式或聲明而不是語句來完成的钦无。

函數(shù)式代碼是冪等的:函數(shù)的返回值只依賴于其參數(shù),相同的參數(shù)永遠(yuǎn)產(chǎn)生相同的返回值盖袭。相反失暂,在命令式編程中,除了函數(shù)的參數(shù)苍凛,全局狀態(tài)也會影響函數(shù)的返回值趣席。消除副作用,即不依賴于函數(shù)輸入的狀態(tài)變化醇蝴,可以使代碼更容易理解宣肚,這是函數(shù)式編程的核心動機(jī)。

前面的話翻譯自維基百科悠栓,有些過于書面化霉涨。下里巴人的話是:

函數(shù)式編程是通過純函數(shù)的組合來構(gòu)建軟件的一種編程方式按价,強(qiáng)調(diào)避免共享狀態(tài)可變數(shù)據(jù)副作用笙瑟。函數(shù)式編程是聲明式的楼镐,而不是命令式的,應(yīng)用狀態(tài)通過純函數(shù)流動往枷。對比面向?qū)ο缶幊炭虿鋺?yīng)用狀態(tài)通常是共享的,并且與對象中的方法關(guān)聯(lián)错洁。

函數(shù)式編程是一種編程范式秉宿,也就是說,它是基于一些基本的屯碴、決定性的原則來構(gòu)建軟件的一種方法描睦。其它的常見編程范式包括面向?qū)ο蟆⒚嫦蜻^程导而、面向協(xié)議等忱叭。注意函數(shù)式編程與面向過程編程的區(qū)別,很多人錯誤地認(rèn)為傳統(tǒng)的C語言編程就是函數(shù)式編程今艺,真是讓人啼笑皆非韵丑。

函數(shù)式代碼通常比命令式或面向?qū)ο蟮拇a更簡潔、可預(yù)測性更強(qiáng)洼滚、更易于測試 -- 不過埂息,如果你對函數(shù)式編程和其常見模式不了解,函數(shù)式代碼也可能看起來頭疼遥巴,因為其信息密度更高。

所有的聲明式編程享幽,概念都相對比較多铲掐,用過Rx的應(yīng)該深有體會。函數(shù)式編程也不例外值桩,涉及到的概念比較多摆霉,學(xué)習(xí)曲線比較陡,隨便來幾個概念感受一下:Monad奔坟、Applicative携栋、Functor。生僻的單詞有沒有激起你想逃跑的本能咳秉?

不要被生僻詞嚇跑婉支,它并沒有看起來那么恐怖。

暫且不用管Monad這種看起來似乎很深奧的家伙澜建,作為初學(xué)者向挖,只要先掌握以下概念即可:

  • 純函數(shù)
  • 函數(shù)組合
  • 避免共享狀態(tài)
  • 避免可變狀態(tài)
  • 避免副作用

請牢記這幾個概念蝌以,這是函數(shù)式編程的核心。

純函數(shù)

函數(shù)式編程的第一個基礎(chǔ)概念就是純函數(shù)何之。純函數(shù)是什么跟畅?如何使函數(shù)變“純”?

如何判斷一個函數(shù)是否為純函數(shù)溶推?對“純”的定義如下:

  • 參數(shù)相同徊件,則返回值相同(也稱為確定性
  • 不會引起任何可觀察的副作用

參數(shù)相同,則返回值相同

讓我們來實現(xiàn)一個計算圓形面積的函數(shù)蒜危。一種非純函數(shù)的實現(xiàn)方式是庇忌,接受一個radius參數(shù),然后返回radius * radius * PI:

let PI = 3.14;
const calculateArea = (radius) => radius * radius * PI;
calculateArea(10); // returns 314.0

為什么這不是純函數(shù)舰褪?因為它使用了一個全局變量皆疹,而不是將其作為參數(shù)傳入。

假設(shè)我們需要提高PI的精度占拍,將其改為3.1415926略就,對于同樣的參數(shù)10,上面函數(shù)的返回值將改變晃酒。

重寫一下:

let PI = 3.14;
const calculateArea = (radius, pi) => radius * radius * pi;
calculateArea(10, PI); // returns 314.0

現(xiàn)在我們將PI作為參數(shù)傳入表牢,除了輸入?yún)?shù),函數(shù)不訪問任何外部對象贝次。毫無疑問崔兴,對于相同的參數(shù),函數(shù)必然返回相同的結(jié)果蛔翅。

讀取文件

如果函數(shù)讀取外部文件敲茄,則函數(shù)不是純函數(shù) — 文件的內(nèi)容可能會改變。

const charactersCounter = (text) => `Character count: ${text.length}`;
function analyzeFile(filename) {
  let fileContent = open(filename);
  return charactersCounter(fileContent);
}

隨機(jī)數(shù)

任何依賴隨機(jī)數(shù)生成的函數(shù)都不是純函數(shù)山析。

function yearEndEvaluation() {
  if (Math.random() > 0.5) {
    return "You get a raise!";
  } else {
    return "Better luck next year!";
  }
}

不產(chǎn)生可觀察的副作用

副作用是除了返回值以外堰燎,在被調(diào)用函數(shù)之外可觀察到的任何狀態(tài)變化。包括:

  • 修改外部變量或?qū)ο蟮膶傩裕ɡ缢窆欤肿兞扛鸭簦负瘮?shù)范圍內(nèi)的變量)
  • 控制臺日志輸出
  • 屏幕輸出
  • 寫文件
  • 輸出到網(wǎng)絡(luò)
  • 調(diào)用外部進(jìn)程
  • 調(diào)用其它含有副作用的函數(shù)

Haskell和其它函數(shù)式編程語言通常使用monads從純函數(shù)中隔離和封裝副作用。monads的概念比較深爵政,足夠?qū)懸槐緯私龇恚信d趣的可以自己查資料了解。

現(xiàn)在我們需要知道的是钾挟,副作用需要與軟件的其它部分隔離洁灵。如果將副作用與其它代碼邏輯分開,軟件將更易于擴(kuò)展等龙、重構(gòu)处渣、調(diào)試伶贰、測試和維護(hù)。這也是大多數(shù)前端框架鼓勵將狀態(tài)管理和組件繪制獨立罐栈、松散耦合地維護(hù)的原因黍衙。

假設(shè)我們要實現(xiàn)一個對整數(shù)加1的函數(shù)。

let counter = 1;

function increaseCounter(value) {
  counter = value + 1;
}

increaseCounter(counter);
console.log(counter); // 2

純函數(shù)實現(xiàn):

let counter = 1;

const increaseCounter = (value) => value + 1;

increaseCounter(counter); // 2
console.log(counter); // 1

函數(shù)式編程不鼓勵可變性荠诬。

如果我們遵循這兩個簡單的原則琅翻,我們的代碼會變得更容易理解。每個函數(shù)都是隔離的柑贞,不會影響系統(tǒng)的其它部分方椎。

純函數(shù)是穩(wěn)定的、一致的和可預(yù)測的钧嘶。給定相同的參數(shù)棠众,純函數(shù)始終返回相同的結(jié)果。我們不需要考慮相同參數(shù)而結(jié)果不同的情況— 因為它永遠(yuǎn)不會發(fā)生有决。

純函數(shù)的好處

代碼明顯更易于測試闸拿。不需要模擬任何東西,所以我們可以用不同的上下文對純函數(shù)進(jìn)行單元測試:

  • 給定參數(shù)A书幕,期望返回值B
  • 給定參數(shù)C新荤,期望返回值D

一個簡單的例子是,函數(shù)接受一個數(shù)字的列表台汇,對列表中的每個數(shù)字加1:

let list = [1, 2, 3, 4, 5];

const incrementNumbers = (list) => list.map(number => number + 1);

接受numbers數(shù)組苛骨,使用map對每個數(shù)字加1,返回新的數(shù)字列表苟呐。

incrementNumbers(list); // [2, 3, 4, 5, 6]

對于輸入[1, 2, 3, 4, 5]痒芝,期望的輸出是[2, 3, 4, 5, 6]

不可變性

不隨時間而改變掠抬,或不可改變吼野。

當(dāng)數(shù)據(jù)不可變時,其狀態(tài)在創(chuàng)建之后不可更改两波。如果需要改變一個不可變對象,那就重新創(chuàng)建一個闷哆。

我們經(jīng)常使用for循環(huán)腰奋,for循環(huán)中有幾個變量:

var values = [1, 2, 3, 4, 5];
var sumOfValues = 0;

for (var i = 0; i < values.length; i++) {
  sumOfValues += values[i];
}

sumOfValues // 15

每次迭代,改變isumOfValue的狀態(tài)抱怔。

如果要求不改變外部狀態(tài)劣坊,應(yīng)該如何做呢?用遞歸屈留。

let list = [1, 2, 3, 4, 5];
let accumulator = 0;

function sum(list, accumulator) {
  if (list.length == 0) {
    return accumulator;
  }

  return sum(list.slice(1), accumulator + list[0]);
}

sum(list, accumulator); // 15
list; // [1, 2, 3, 4, 5]
accumulator; // 0

初看可能不太容易理解局冰,sub函數(shù)接受一個數(shù)字列表以及當(dāng)前累加值测蘑,每次迭代將第一個元素加到累加值上,然后用其它元素和新的累加值進(jìn)行下一輪迭代康二,直到列表為空碳胳。

通過使用迭代,我們保持了狀態(tài)的不變性沫勿。listaccmulator在運算過程中沒有發(fā)生變化挨约。

可以使用reduct實現(xiàn)此函數(shù),見后面高階函數(shù)部分产雹。

注意:此處的代碼其實變得更難以理解了诫惭,這也是純函數(shù)式編程難以流行的原因。真正的高手蔓挖,打的都是組合拳夕土,適可而止很重要,否則過猶不及瘟判。

對各種對象做轉(zhuǎn)換是很常見的任務(wù)怨绣。比如,有一個字符串荒适,我們需要將其轉(zhuǎn)換為url slug(看一下瀏覽器的地址欄梨熙,用-分割的名稱就是url slug)。

下面是Ruby的OOP實現(xiàn)刀诬,我們創(chuàng)建一個類UrlSlugify咽扇,此類有一個slugify方法用來實現(xiàn)此轉(zhuǎn)換。

class UrlSlugify
  attr_reader :text
  
  def initialize(text)
    @text = text
  end

  def slugify!
    text.downcase!
    text.strip!
    text.gsub!(' ', '-')
  end
end

UrlSlugify.new(' I will be a url slug   ').slugify! # "i-will-be-a-url-slug"

此處是命令式編程陕壹,明確說明了slugify的步驟 — 首先轉(zhuǎn)小寫质欲,然后移除無用的空格,最后用連字符替換剩余的空格糠馆。

在處理過程中嘶伟,我們改變了輸入狀態(tài)。

不改變輸入狀態(tài)的方式應(yīng)該如何實現(xiàn)又碌?可以用函數(shù)組合或函數(shù)鏈九昧。函數(shù)的結(jié)果作為下一個函數(shù)的輸入,而不改變原始輸入毕匀。

const string = " I will be a url slug   ";

const slugify = string =>
  string
    .toLowerCase()
    .trim()
    .split(" ")
    .join("-");

slugify(string); // i-will-be-a-url-slug

此處:

  • toLowerCase:將字符串轉(zhuǎn)化為小寫
  • trim:移除字符串兩端的空白字符
  • splitjoin:替換匹配字符

通過以上四個函數(shù)的組合铸鹰,我們實現(xiàn)了slugify

引用透明性

我們來實現(xiàn)一個平方函數(shù):

const square = (n) => n * n;

給定相同的輸入皂岔,此純函數(shù)總是返回相同的結(jié)果蹋笼。

square(2); // 4
square(2); // 4
square(2); // 4
// ...

給定參數(shù)2square函數(shù)總是返回4。所以我們可以用4替換square(2)剖毯,這就叫引用透明性圾笨。

基本上,如果一個函數(shù)對于相同的輸入逊谋,總是產(chǎn)生相同的結(jié)果擂达,那么就可以說它是引用透明的。

純函數(shù) + 不可變數(shù)據(jù) = 引用透明性

有了這個概念涣狗,我們就可以將函數(shù)“記憶化”(一種程序優(yōu)化技術(shù)谍婉,通過緩存復(fù)雜函數(shù)的結(jié)果,當(dāng)輸入相同時直接返回緩存結(jié)果镀钓,以此來提升程序的運行速度)穗熬。

假設(shè)有以下函數(shù):

const sum = (a, b) => a + b;

有如下調(diào)用:

sum(3, sum(5, 8));

sum(5, 8)等于13,此函數(shù)的結(jié)果總是13丁溅。因此上面的表達(dá)式等于:

sum(3, 13);

此表達(dá)式的結(jié)果總是16唤蔗。我們可以將整個表達(dá)式用一個數(shù)字常量來替換,將其記憶化窟赏。

函數(shù)是第一類對象

函數(shù)是第一類對象妓柜,是說函數(shù)被作為值對待,可作為普通數(shù)據(jù)使用涯穷。

函數(shù)作為第一類對象使得可以:

  • 使用常量或變量引用它
  • 將其作為參數(shù)傳入其它函數(shù)
  • 將其作為函數(shù)返回值

此思想將函數(shù)視為值棍掐,并作為數(shù)據(jù)傳遞。通過這種方式拷况,我們可以組合不同的函數(shù)來創(chuàng)建具有新行為的新函數(shù)作煌。

假設(shè)有一個函數(shù),對兩個輸入值求和并加倍:

const doubleSum = (a, b) => (a + b) * 2;

另一個函數(shù)赚瘦,對兩個輸入值求差值并加倍:

const doubleSubtraction = (a, b) => (a - b) * 2;

兩個函數(shù)邏輯類似粟誓,不同之處在于運算符函數(shù)。如果我們可以將函數(shù)作為值起意,并作為參數(shù)傳遞鹰服,我們可以構(gòu)建一個函數(shù),運算符函數(shù)作為其參數(shù)揽咕。

const sum = (a, b) => a + b;
const subtraction = (a, b) => a - b;

const doubleOperator = (f, a, b) => f(a, b) * 2;

doubleOperator(sum, 3, 1); // 8
doubleOperator(subtraction, 3, 1); // 4

此處有運算符函數(shù)參數(shù)f悲酷,用于處理參數(shù)ab。通過sumsubtraction函數(shù)與doubleOperator函數(shù)的組合創(chuàng)造了新的行為亲善。

高階函數(shù)

當(dāng)我們討論高階函數(shù)時舔涎,我們指的是這樣一個函數(shù):

  • 將一個或多個函數(shù)作為參數(shù),或
  • 返回一個函數(shù)作為返回值

上面的doubleOperator函數(shù)是一個高階函數(shù)逗爹,因為其將運算符函數(shù)作為參數(shù)。

你可能已經(jīng)聽說過filtermapreduce掘而,最常用的三個操作容器(數(shù)組挟冠、字典等)的高階函數(shù),我們再一起看一下袍睡。

Filter

給定一個集合知染,我們希望根據(jù)某個屬性做篩選。filter函數(shù)期望一個布爾值斑胜,用于判定元素是否應(yīng)該包含在結(jié)果集合中控淡。如果回調(diào)函數(shù)返回true,那么當(dāng)前元素將會包含在結(jié)果集合中止潘,否則不會掺炭。

一個簡單的例子是,我們希望從一個整數(shù)列表中篩選出所有偶數(shù)凭戴。

命令式

命令式的實現(xiàn)方式:

  • 創(chuàng)建一個空的evenNumbers數(shù)組
  • 遍歷numbers數(shù)組
  • 將偶數(shù)放入evenNumbers數(shù)組中
var numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var evenNumbers = [];

for (var i = 0; i < numbers.length; i++) {
  if (numbers[i] % 2 == 0) {
    evenNumbers.push(numbers[i]);
  }
}

console.log(evenNumbers); // (6) [0, 2, 4, 6, 8, 10]

我們也可以使用filter高階函數(shù)涧狮,向其傳遞一個even函數(shù):

const even = n => n % 2 == 0;
const listOfNumbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
listOfNumbers.filter(even); // [0, 2, 4, 6, 8, 10]

Hacker Rank FP(類似于LeetCode、琶捶颍客網(wǎng))上有一個Filter數(shù)組問題者冤,過濾一個整數(shù)數(shù)組,輸出小于x的所有數(shù)字档痪。

一種命令式解法可以是:

var filterArray = function(x, coll) {
  var resultArray = [];

  for (var i = 0; i < coll.length; i++) {
    if (coll[i] < x) {
      resultArray.push(coll[i]);
    }
  }

  return resultArray;
}

console.log(filterArray(3, [10, 9, 8, 2, 7, 5, 1, 3, 0])); // (3) [2, 1, 0]

精確地說明函數(shù)需要做什么 - 遍歷集合涉枫,將當(dāng)前數(shù)字與x比較,如果滿足條件則放入結(jié)果數(shù)組resultArray腐螟。

聲明式

如果我們想要使用filter高階函數(shù)的更加聲明化的解法呢愿汰?

一種聲明式的解法可以是:

function smaller(number) {
  return number < this;
}

function filterArray(x, listOfNumbers) {
  return listOfNumbers.filter(smaller, x);
}

let numbers = [10, 9, 8, 2, 7, 5, 1, 3, 0];

filterArray(3, numbers); // [2, 1, 0]

smaller函數(shù)中使用this初看起來可能有點兒怪,不過它本身很好理解 (JS的騷語法遭垛,其它常見編程語言未必有類似用法)尼桶。filter函數(shù)的第二個參數(shù)會成為this,在上例中锯仪,this表示3(x)泵督。

也可以這樣操作maps。假設(shè)有一個包含nameage的map:

let people = [
  { name: "TK", age: 26 },
  { name: "Kaio", age: 10 },
  { name: "Kazumi", age: 30 }
];

我們想要篩選出所有21歲以上的人:

const olderThan21 = person => person.age > 21;
const overAge = people => people.filter(olderThan21);
overAge(people); // [{ name: 'TK', age: 26 }, { name: 'Kazumi', age: 30 }]

Map

map用來對集合做轉(zhuǎn)換庶喜。

map將一個函數(shù)應(yīng)用于集合的所有元素小腊,根據(jù)函數(shù)的結(jié)果構(gòu)建一個新的集合,以實現(xiàn)對集合的轉(zhuǎn)換久窟。

還是上面的people數(shù)組秩冈,不過此次不是根據(jù)年齡做篩選,而是想得到每個人的描述斥扛,類似TK is 26 years old入问。也就是說,對于每個人,我們希望得到一個字符串:name is :age years old芬失,其中:name:agepeople數(shù)組中每個元素的屬性楣黍。

一個命令式的實現(xiàn)方式:

var people = [
  { name: "TK", age: 26 },
  { name: "Kaio", age: 10 },
  { name: "Kazumi", age: 30 }
];

var peopleSentences = [];

for (var i = 0; i < people.length; i++) {
  var sentence = people[i].name + " is " + people[i].age + " years old";
  peopleSentences.push(sentence);
}

console.log(peopleSentences); // ['TK is 26 years old', 'Kaio is 10 years old', 'Kazumi is 30 years old']

而聲明式的實現(xiàn)方式可能是:

const makeSentence = (person) => `${person.name} is ${person.age} years old`;

const peopleSentences = (people) => people.map(makeSentence);
  
peopleSentences(people);
// ['TK is 26 years old', 'Kaio is 10 years old', 'Kazumi is 30 years old']

Hacker Rank上有一個修改列表問題,將數(shù)組中的每個數(shù)字修改為其絕對值棱烂。

例如租漂,對于數(shù)組[1, 2, 3, -4, 5],結(jié)果應(yīng)該是[1, 2, 3, 4, 5]颊糜。

一種簡單的實現(xiàn)方案是對集合中的每個數(shù)進(jìn)行就地更新:

var values = [1, 2, 3, -4, 5];

for (var i = 0; i < values.length; i++) {
  values[i] = Math.abs(values[i]);
}

console.log(values); // [1, 2, 3, 4, 5]

我們使用Math.abs函數(shù)將每個數(shù)轉(zhuǎn)換為其絕對值哩治,然后就地更新。

不是函數(shù)式的實現(xiàn)方式衬鱼。

我們前面介紹了不可變性业筏,強(qiáng)調(diào)了不可變性對函數(shù)一致性、可預(yù)測性的重要作用馁启。既然想要構(gòu)建一個絕對值組成的新集合驾孔,為什么不用map呢?

首先看一下Math.abs函數(shù)如何操作一個數(shù)字:

Math.abs(-1); // 1
Math.abs(1); // 1
Math.abs(-2); // 2
Math.abs(2); // 2

Math.abs將數(shù)字轉(zhuǎn)換為其絕對值惯疙。

我們已經(jīng)知道如何獲取一個數(shù)字的絕對值翠勉,我們可以將此函數(shù)傳入map函數(shù)。還記得高階函數(shù)可以接受其它函數(shù)作為其參數(shù)嗎霉颠?

let values = [1, 2, 3, -4, 5];
const updateListMap = (values) => values.map(Math.abs);
updateListMap(values); // [1, 2, 3, 4, 5]

是不是簡潔对碌、優(yōu)雅很多?

Reduce

接受一個函數(shù)和一個集合蒿偎,將其組合為一個新的值朽们。

一個常見的例子是,獲取一個訂單的總額诉位。假設(shè)我們在某個電商網(wǎng)站上骑脱,將Product 1Product 2苍糠、Product 3Product 4加入了購物車(訂單)叁丧,我們想要計算購物車商品的總金額。

一種命令式的實現(xiàn)方式是岳瞭,遍歷商品列表拥娄,累加商品的金額。

var orders = [
  { productTitle: "Product 1", amount: 10 },
  { productTitle: "Product 2", amount: 30 },
  { productTitle: "Product 3", amount: 20 },
  { productTitle: "Product 4", amount: 60 }
];

var totalAmount = 0;

for (var i = 0; i < orders.length; i++) {
  totalAmount += orders[i].amount;
}

console.log(totalAmount); // 120

使用reduce函數(shù)瞳筏,我們可以構(gòu)建一個用來amount sum的函數(shù)稚瘾,并將其傳給reduce函數(shù)。

let shoppingCart = [
  { productTitle: "Product 1", amount: 10 },
  { productTitle: "Product 2", amount: 30 },
  { productTitle: "Product 3", amount: 20 },
  { productTitle: "Product 4", amount: 60 }
];

const sumAmount = (currentTotalAmount, order) => currentTotalAmount + order.amount;

const getTotalAmount = (shoppingCart) => shoppingCart.reduce(sumAmount, 0);

getTotalAmount(shoppingCart); // 120

此處姚炕,我們有購物車商品列表shoppingCart摊欠,函數(shù)sumAmount接受當(dāng)前總額currentTotalAmount和訂單order對象以執(zhí)行計算丢烘。

getTotalAmount函數(shù)使用sumAmount和起始值0對shoppingCartreduce

另外一種獲取總額的方式是組合mapreduce函數(shù)凄硼。什么意思呢铅协?我們可以首先使用mapshoppingCart轉(zhuǎn)換為amount的數(shù)組,然后再使用sumAmount函數(shù)做reduce摊沉。

const getAmount = (order) => order.amount;
const sumAmount = (acc, amount) => acc + amount;

function getTotalAmount(shoppingCart) {
  return shoppingCart
    .map(getAmount)
    .reduce(sumAmount, 0);
}

getTotalAmount(shoppingCart); // 120

getAmount函數(shù)接受訂單對象order并返回金額amount。此處痒给,我們有[10, 30, 30, 60]说墨。然后reduce累加所有的值以得到結(jié)果。漂亮苍柏!

我們再來看一個將上面講解的幾個高階函數(shù)組合應(yīng)用的例子尼斧。

依然以購物車shopping cart為例,假設(shè)購物車中有以下商品:

let shoppingCart = [
  { productTitle: "Functional Programming", type: "books", amount: 10 },
  { productTitle: "Kindle", type: "eletronics", amount: 30 },
  { productTitle: "Shoes", type: "fashion", amount: 20 },
  { productTitle: "Clean Code", type: "books", amount: 60 }
]

我們希望獲得購物車中所有書籍的總額试吁。如何做呢棺棵?

  • 篩選出所有的書籍(filter)
  • 將商品列表轉(zhuǎn)換為金額的列表(map)
  • 將所有值累加(reduce)
let shoppingCart = [
  { productTitle: "Functional Programming", type: "books", amount: 10 },
  { productTitle: "Kindle", type: "eletronics", amount: 30 },
  { productTitle: "Shoes", type: "fashion", amount: 20 },
  { productTitle: "Clean Code", type: "books", amount: 60 }
]

const byBooks = (order) => order.type == "books";
const getAmount = (order) => order.amount;
const sumAmount = (acc, amount) => acc + amount;

function getTotalAmount(shoppingCart) {
  return shoppingCart
    .filter(byBooks)
    .map(getAmount)
    .reduce(sumAmount, 0);
}

getTotalAmount(shoppingCart); // 70

參考資源

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市熄捍,隨后出現(xiàn)的幾起案子烛恤,更是在濱河造成了極大的恐慌,老刑警劉巖余耽,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缚柏,死亡現(xiàn)場離奇詭異,居然都是意外死亡碟贾,警方通過查閱死者的電腦和手機(jī)币喧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來袱耽,“玉大人杀餐,你說我怎么就攤上這事≈炀蓿” “怎么了史翘?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蔬崩。 經(jīng)常有香客問我恶座,道長,這世上最難降的妖魔是什么沥阳? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任跨琳,我火速辦了婚禮,結(jié)果婚禮上桐罕,老公的妹妹穿的比我還像新娘脉让。我一直安慰自己桂敛,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布溅潜。 她就那樣靜靜地躺著术唬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪滚澜。 梳的紋絲不亂的頭發(fā)上粗仓,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天,我揣著相機(jī)與錄音设捐,去河邊找鬼借浊。 笑死,一個胖子當(dāng)著我的面吹牛萝招,可吹牛的內(nèi)容都是我干的蚂斤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼槐沼,長吁一口氣:“原來是場噩夢啊……” “哼曙蒸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起岗钩,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤纽窟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后凹嘲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體师倔,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年周蹭,在試婚紗的時候發(fā)現(xiàn)自己被綠了趋艘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡凶朗,死狀恐怖瓷胧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情棚愤,我是刑警寧澤搓萧,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站宛畦,受9級特大地震影響瘸洛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜次和,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一反肋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧踏施,春花似錦石蔗、人聲如沸罕邀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诉探。三九已至,卻和暖如春棍厌,著一層夾襖步出監(jiān)牢的瞬間肾胯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工定铜, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留阳液,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓揣炕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親东跪。 傳聞我的和親對象是個殘疾皇子畸陡,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,871評論 2 354

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