第一章:
函數(shù)式編程主要基于數(shù)學(xué)函數(shù)和它的思想。
1.1 函數(shù)與js方法:
函數(shù)是一段可以通過(guò)其名稱(chēng)被調(diào)用的代碼,可以傳遞參數(shù)并返回值。
方法是一段必須通過(guò)其名稱(chēng)及其關(guān)聯(lián)對(duì)象的名稱(chēng)被調(diào)用的代碼。
//函數(shù)
var func = (a)=>{return a}
func(5) //用其名稱(chēng)調(diào)用
//方法
var obj = {simple:(a)=>{return a}}
obj.simple(5) //用其名稱(chēng)及其關(guān)聯(lián)對(duì)象調(diào)用
1.2 引用透明性
所有函數(shù)對(duì)于相同的輸入都將返回相同的值(函數(shù)只依賴(lài)參數(shù)的輸入,不依賴(lài)于其他全局?jǐn)?shù)據(jù),即函數(shù)內(nèi)部沒(méi)有全局引用),這使并行代碼和緩存(用值直接替換函數(shù)的結(jié)果)成為可能。
1.3 命令式、聲明式與抽象
命令式主張告訴編譯器“如何”做
var array = [1,2,3]
for(let i=0; o<array.length;i++){
console.log(array[i])
}
聲明式告訴編譯器“做什么”,如何做的部分(獲得數(shù)組長(zhǎng)度,循環(huán)遍歷每一項(xiàng))被抽象到高階函數(shù)中,forEach就是這樣一個(gè)內(nèi)置函數(shù),本書(shū)中我們都將創(chuàng)建這樣的內(nèi)置函數(shù)。
var array = [1,2,3]
array.forEach(elememt=>console.log(elememt))
1.4 函數(shù)式編程的好處&純函數(shù)
好處就是編寫(xiě)純函數(shù)。純函數(shù)是對(duì)相同輸入返回相同輸出的函數(shù),不依賴(lài)(包含)任何外部變量,所以也不會(huì)產(chǎn)生改變外部環(huán)境變量的副作用。
1.5 并行代碼
純函數(shù)允許我們并行執(zhí)行代碼,因?yàn)榧兒瘮?shù)不會(huì)改變它的環(huán)境,所以不需要擔(dān)心同步問(wèn)題。當(dāng)然,js并沒(méi)有真正的多線程支持并行,但如果你的項(xiàng)目使用了webworker來(lái)模擬多線程并行執(zhí)行任務(wù),這種時(shí)候就需要用純函數(shù)來(lái)代替非純函數(shù)。
let global = 'something';
let function1 = (input) => {
global = "somethingElse"
}
let function2 = ()=>{
if(global === 'something'){
//業(yè)務(wù)邏輯
}
}
如果我們要兩個(gè)線程并行執(zhí)行function1和function2,由于兩個(gè)函數(shù)都依賴(lài)全局變量global,并行執(zhí)行就會(huì)引起不良的影響(兩個(gè)函數(shù)的執(zhí)行順序不同會(huì)有不同的結(jié)果),現(xiàn)在把它們改為純函數(shù)。
let function1 = (input,global)=>{
global = "somethingElse"
}
let function2 = (global)=>{
if(global === "something"){
//業(yè)務(wù)邏輯
}
}
我們移動(dòng)了global變量,把它作為兩個(gè)函數(shù)的參數(shù),使他們變成純函數(shù)?,F(xiàn)在并行執(zhí)行不會(huì)有任何問(wèn)題,由于函數(shù)不依賴(lài)于外部環(huán)境變量,不必?fù)?dān)心線程的執(zhí)行順序。
1.6 可緩存
根據(jù)純函數(shù)對(duì)于給定輸入總是返回相同的輸出,我們可以緩存函數(shù)的輸出,減少多次的輸入來(lái)反復(fù)調(diào)用函數(shù)。
var longRunningFnBookKeeper = {2:3,4:5...}
longRunningFnBookKeeper.hasOwnProperty(ip)?longRunningFnBookKeeper[ip]:longRunningFunction(ip)
1.7 管道與組合
純函數(shù)應(yīng)該被設(shè)計(jì)為:只做一件事。實(shí)現(xiàn)多個(gè)功能通過(guò)函數(shù)的組合來(lái)實(shí)現(xiàn)。
UNIX/LINUX中,在一個(gè)文件中找到一個(gè)特定的名稱(chēng)并統(tǒng)計(jì)它的出現(xiàn)次數(shù):
cat jsBook | grep -i "composing" | wc
組合不是命令行特有的,它是函數(shù)式編程的核心。
1.8 關(guān)于js
js是一門(mén)面對(duì)對(duì)象的語(yǔ)言,不是一種純函數(shù)語(yǔ)言,更像是一種多范式語(yǔ)言,但是非常適合函數(shù)式編程。
第二章:js函數(shù)基礎(chǔ)
今天很多瀏覽器還不支持ES6,我們可以通過(guò)轉(zhuǎn)換編譯器babel,將ES6轉(zhuǎn)換為ES5代碼。

可以看到,箭頭函數(shù)的this經(jīng)過(guò)編譯后為undefined,轉(zhuǎn)換后的代碼運(yùn)行在嚴(yán)格模式下,嚴(yán)格模式是js的受限變體。
"use strict"
a = 1 // -> Uncaught ReferenceError: a is not defined; 此處直接報(bào)錯(cuò)
在函數(shù)內(nèi)部如果用var聲明變量和不用時(shí)有很大差別,用var聲明的是局部變量,在函數(shù)外部訪問(wèn)這個(gè)變量是訪問(wèn)不到的,沒(méi)var聲明的是全局變量。在函數(shù)外部是可以訪問(wèn)到的。
如果你不使用var命令指定,在全局狀態(tài)下定一個(gè)變量。在嚴(yán)格模式下這段代碼會(huì)報(bào)錯(cuò),因?yàn)槿肿兞吭趈s中非常有害。
第三章:高階函數(shù)
高階函數(shù)(HOC):
- 接收函數(shù)作為參數(shù)
- 返回函數(shù)作為輸出
- 接收函數(shù)作為參數(shù)且返回函數(shù)作為輸出
滿(mǎn)足以上三個(gè)之一的函數(shù)就是高階函數(shù)。
3.1 理解數(shù)據(jù)
3.1.1 js中函數(shù)為一等公民:
因?yàn)楹瘮?shù)也是js中的一種數(shù)據(jù)類(lèi)型,可以被賦值給變量,作為參數(shù)傳遞,也可被其他函數(shù)返回。
3.1.2 把一個(gè)函數(shù)存入變量
let fn = () => {} //fn就是一個(gè)指向函數(shù)數(shù)據(jù)類(lèi)型的變量,即函數(shù)的引用
fn() //調(diào)用函數(shù),即執(zhí)行fn指向的函數(shù)
3.1.3 函數(shù)作為參數(shù)傳入
var tellType = arg =>{
if(typeof arg==='function'){
arg() //如果傳入的是函數(shù)就執(zhí)行
}else{
console.log(arg) //否則就輸出數(shù)據(jù)
}
}
var fn = () => {console.log('i am a function')}
tellType(fn) //函數(shù)作為參數(shù)傳入
3.1.4 返回函數(shù)
String是js的內(nèi)置函數(shù),注意:只返回了函數(shù)的引用,并沒(méi)有執(zhí)行函數(shù)
let crazy = () =>{ return String }
crazy() // String() { [native code] }
crazy()('HOC') // "HOC"
3.2 抽象和高階函數(shù)
高階函數(shù)就是定義抽象
3.2.1通過(guò)高階函數(shù)實(shí)現(xiàn)抽象
forEach實(shí)現(xiàn)遍歷數(shù)組
const forEach = (array,fn)=>{
for(let i=0;i<array.length;i++){
fn(array[i])
}
}
forEachObject實(shí)現(xiàn)遍歷對(duì)象
const forEachObject = (obj,fn)=>{
for(var property in obj){
if(obj.hasOwnProperty(properity){
fn(property,obj[property])
})
}
}
注意:forEach和forEachObject都是高階函數(shù),他們使開(kāi)發(fā)者專(zhuān)注于任務(wù),而抽象出遍歷的部分。
unless函數(shù):如果predicate為false,則調(diào)用fn
const unless = (predicate,fn)=>{
if(!predicate)
fn()
}
查找一個(gè)列表中的偶數(shù)
forEach([1,2,3,4,6,7],(number)=>{
unless((number%2),()=>{
console.log(number,"is even")
})
})
如果我們操作的是一個(gè)Number而不是array
const times = (time,fn)=>{
for(var i=0;i<time;i++){
fn(i)
}
}
times(100,function(n){
unless(n%2,function(){
console.log(n, "is even")
})
})
3.3 真實(shí)的高階函數(shù)
a.every(function(element, index, array))
every是所有函數(shù)的每個(gè)回調(diào)函數(shù)都返回true的時(shí)候才會(huì)返回true,當(dāng)遇到false的時(shí)候終止執(zhí)行,返回false。
a.some(function(element, index, array))
some函數(shù)是“存在”有一個(gè)回調(diào)函數(shù)返回true的時(shí)候終止執(zhí)行并返回true,否則返回false
在空數(shù)組上調(diào)用every返回true,some返回false。
3.3.1 every函數(shù)
every函數(shù)接受兩個(gè)參數(shù):一個(gè)數(shù)組和一個(gè)函數(shù)。它使用傳入的函數(shù)檢查數(shù)組的所有元素是否為true, 都為true才返回true
const every = (arr,fn)=>{
let result = true;
for(let i =0;i<arr.length;i++){
result = result&&fn(arr[i])
}
return true;
}
every([NaN,NaN,NaN],isNaN)
for..of循環(huán):ES6中用于遍歷數(shù)組元素的方法,重寫(xiě)every方法
const every = (arr,fn)=>{
let result = true;
for(const element of arr){
result = result&&fn(element)
}
return true;
}
3.3.2 some函數(shù)
some函數(shù)接受兩個(gè)參數(shù):一個(gè)數(shù)組和一個(gè)函數(shù)。它使用傳入的函數(shù)檢查數(shù)組的所有元素是否為true, 只要有一個(gè)為true就返回true
const some = (arr,fn)=>{
let result = false;
for(const element of arr){
result = result||fn(element)
}
return true;
}
some([5,NaN,NaN],isNaN)
3.3.3 sort函數(shù)
sort函數(shù)是一個(gè)高階函數(shù),它接受一個(gè)函數(shù)作為參數(shù),該函數(shù)幫助sort函數(shù)決定排序邏輯, 是一個(gè)改變?cè)瓟?shù)組的方法。
arr.sort([compareFunc])
compareFunc是可選的,如果compareFunc未提供,元素將被轉(zhuǎn)換為字符串并按Unicode編碼點(diǎn)順序排列。
compareFunc應(yīng)該實(shí)現(xiàn)下面的邏輯
function compareFunc(a,b){
if(根據(jù)某種排序標(biāo)準(zhǔn)a<b){
return -1
}
if(根據(jù)某種排序標(biāo)準(zhǔn)a>b){
return 1
}
return 0;
}
具體例子
var friends = [{name: 'John', age: 30},
{name: 'Ana', age: 20},
{name: 'Chris', age: 25}];
function compareFunc(a,b){
return (a.age<b.age)?-1:(a.age>b.age)?1:0
}
寫(xiě)成以下也ok,按照age升序排列
function compareFunc(a,b){
return a.age>b.age
}
friends.sort(compareFunc)

如果要比較不同的屬性,我們需要重復(fù)編寫(xiě)比較代碼。下面新建一個(gè)sortBy函數(shù),允許用戶(hù)基于傳入的屬性對(duì)對(duì)象數(shù)組排序。
const sortBy = (property)=>{
return (a,b) => {
return (a[property]<b[property])?-1:(a[property]>b[property])?1:0
}
}
var friends = [{name: 'John', age: 30},
{name: 'Ana', age: 20},
{name: 'Chris', age: 25}];
friends.sort(sortBy('age'))
注意:sortBy函數(shù)接受一個(gè)屬性冰返回另一個(gè)函數(shù),這個(gè)返回的函數(shù)就作為compareFunc傳遞給sort函數(shù),持有property參數(shù)值的返回函數(shù)之所以能夠運(yùn)行是因?yàn)閖s支持閉包。
第四章:高階函數(shù)與閉包
4.1理解閉包
4.1.1什么是閉包
簡(jiǎn)言之,閉包是一個(gè)內(nèi)部函數(shù),它是在一個(gè)函數(shù)內(nèi)部的函數(shù)。
function outer(){
function inner(){
}
}
函數(shù)inner稱(chēng)為閉包函數(shù),閉包如此強(qiáng)大的原因在于它對(duì)作用域鏈的訪問(wèn)。
閉包有3個(gè)可以訪問(wèn)的作用域:
1.閉包函數(shù)內(nèi)聲明的變量
2.對(duì)全局變量的訪問(wèn)
3.對(duì)外部函數(shù)變量的訪問(wèn)?。。?!
let global = 'global';//2
function outer(){
let outer = 'outer';
function inner(){
let a=5;//1
console.log(outer) //3.閉包能夠訪問(wèn)外部函數(shù)變量
}
return inner
}
outer()()//"outer"
4.1.2 閉包可以記住它的上下文
var fn = (arg)=>{
let outer = 'outer';
let innerFn = () =>{
console.log(outer)
console.log(arg)
}
return innerFn
}
var closeureFn = fn(5)
closeureFn()//outer 5
當(dāng)執(zhí)行var closeureFn = fn(5)時(shí),函數(shù)innerFn被返回,js執(zhí)行引擎視innerFn為一個(gè)閉包,并相應(yīng)的設(shè)置了它的作用域。3個(gè)作用域?qū)蛹?jí)在innerFn返回時(shí)都被設(shè)置了。
如此,closeureFn()通過(guò)作用域鏈被調(diào)用時(shí)就記住了arg、outer的值。
我們回到sortBy
const sortBy = (property)=>{
return (a,b) => {
return (a[property]<b[property])?-1:(a[property]>b[property])?1:0
}
}
當(dāng)我們以如下形式調(diào)用時(shí)
sortBy('age')
發(fā)生下面的事情:
sortBy函數(shù)返回了一個(gè)接受兩個(gè)參數(shù)的新函數(shù),這個(gè)新函數(shù)就是一個(gè)閉包
(a,b)=>{/*實(shí)現(xiàn)*/}
根據(jù)閉包能訪問(wèn)作用域?qū)蛹?jí)的特點(diǎn),它能在它的上下文中持有property的值,所以它將在合適并且需要的時(shí)候使用返回值。
4.2真實(shí)的高階函數(shù)
4.2.1 once:允許只運(yùn)行一次給定的函數(shù)
這在開(kāi)發(fā)過(guò)程中很常見(jiàn),例如只想設(shè)置一次第三方庫(kù),初始化一次支付設(shè)置。
const once = (fn)=>{
let done = false;
return function(){
return done?undefined:((done=true),fn.apply(this,arguments))
}
}
var dopayment = once(()=>{console.log("Payment is done")})
dopayment() //Payment is done
dopayment() //undefined
js中,(exp1,exp2)的含義是執(zhí)行兩個(gè)參數(shù)并返回第二個(gè)表達(dá)式的結(jié)果。
注意:once函數(shù)接受一個(gè)參數(shù)fn并通過(guò)調(diào)用fn的apply方法返回結(jié)果。我們聲明了done變量,返回的函數(shù)會(huì)形成一個(gè)覆蓋它的閉包作用域,檢查done是否為true,如果是則返回undefined,
否則將done設(shè)為true,如此就阻止了下一次的執(zhí)行。
4.2.2 memoized
用于為每一個(gè)輸入存儲(chǔ)結(jié)果,以便于重用函數(shù)中的計(jì)算結(jié)果。
const memoized = (fn) => {
const lookupTable = {};
return (arg) => lookupTable[arg] || (lookupTable[arg]=fn(arg));
}
有一個(gè)名為lookupTable的局部變量,它在返回函數(shù)的閉包上下文中。返回函數(shù)將接受一個(gè)參數(shù)并檢查它是否在lookupTable中。
如果在,就返回對(duì)應(yīng)的值,否則使用新的輸入作為key,fn(arg)的結(jié)果為value,更新lookupTable對(duì)象。
求函數(shù)的階乘(遞歸法)
var factorial = (n) => {
if(n===0){
return 1;
}
return n*factorial(n-1)
}
現(xiàn)在可以改為把factorial函數(shù)包裹進(jìn)一個(gè)memoized函數(shù)來(lái)保留它的輸出(存儲(chǔ)結(jié)果法)
let factorial = memoized((n)=>{
if(n===0){
return 1;
}
return n*factorial(n-1)
})
它以同樣的方式運(yùn)行,但是比之前快的多。
第五章:數(shù)組的函數(shù)式編程
我們使用數(shù)組來(lái)存儲(chǔ)、操作和查找數(shù)據(jù),以及轉(zhuǎn)換(投影)數(shù)據(jù)格式。本章中使用函數(shù)式編程來(lái)改進(jìn)這些操作。
5.1 數(shù)組的函數(shù)式方法
本節(jié)創(chuàng)建的所有函數(shù)稱(chēng)為投影函數(shù),把函數(shù)應(yīng)用于一個(gè)值并創(chuàng)建一個(gè)新值的過(guò)程稱(chēng)為投影。
5.1.1 map
首先來(lái)看遍歷數(shù)組的forEach方法
const forEach = (array,fn) => {
for(const value of array)
fn(value)
}
map函數(shù)的實(shí)現(xiàn)代碼如下
const map = (array,fn) => {
let results= [];
for(const value of array)
results.push(fn(value))
return results;
}
map和forEach非常類(lèi)似,區(qū)別是用一個(gè)新的數(shù)組捕獲了結(jié)果,并返回了結(jié)果。
let apressBooks = [
{
"id": 111,
"title": "c# 6.0",
"author": "ANDREW JKDKS",
"rating": [4],
"reviews": [{good:4, excellent: 12}]
},
{
"id": 222,
"title": "Machine Learning",
"author": "ANDREW JKDKS",
"rating": [3],
"reviews": [{good:4, excellent: 12}]
},
{
"id": 333,
"title": "Angularjs",
"author": "ANDREW JKDKS",
"rating": [5],
"reviews": [{good:4, excellent: 12}]
},
{
"id": 444,
"title": "Pro ASP.NET",
"author": "ANDREW JKDKS",
"rating": [4.7],
"reviews": [{good:4, excellent: 12}]
}
];
假設(shè)只需要獲取包含title和author的字段
map(apressBooks,(book)=>{
return {title:book.title,author:book.author}
})
5.1.2 filter
有時(shí)我們還想過(guò)濾數(shù)組的內(nèi)容(例如獲取rating>4.5的圖書(shū)列表),再轉(zhuǎn)換為一個(gè)新數(shù)組,因此我們需要一個(gè)類(lèi)似map的函數(shù),它只需要在把結(jié)果放入數(shù)組前檢查一個(gè)條件。
const filter = (array,fn) => {
let results= [];
for(const value of array)
fn(value) ? results.push(value) : undefined
return results;
}
調(diào)用高階函數(shù)filter
filter(apressBooks, (book)=>book.rating[0]>4.5)
返回結(jié)果

5.2 連接操作
map和filter都是投影函數(shù),因此它們總是對(duì)數(shù)組應(yīng)用轉(zhuǎn)換操作后再返回?cái)?shù)據(jù),于是我們能夠連接filter和map(注意順序)來(lái)完成任務(wù)而不需要額外變量。
例如:從apressBooks中獲取含有title和author對(duì)象且評(píng)級(jí)高于4.5的對(duì)象。
map(filter(apressBooks, (book)=>book.rating[0]>4.5),(book)=>{
return {title:book.title,author:book.author}
})
我們將后面的章節(jié)中國(guó)通過(guò)函數(shù)組合來(lái)完成同樣的事情。
concatAll
對(duì)apressBooks對(duì)象稍作修改,得到如下數(shù)據(jù)結(jié)構(gòu)
let apressBooks = [
{
name: "beginners",
bookDetails: [
{
"id": 111,
"title": "c# 6.0",
"author": "ANDREW 1",
"rating": [4],
"reviews": [{good:4, excellent: 12}]
},
{
"id": 222,
"title": "Machine Learning",
"author": "ANDREW 2",
"rating": [3],
"reviews": [{good:4, excellent: 12}]
}
]
},
{
name: "pro",
bookDetails: [
{
"id": 333,
"title": "Angularjs",
"author": "ANDREW 3",
"rating": [5],
"reviews": [{good:4, excellent: 12}]
},
{
"id": 444,
"title": "Pro ASP.NET",
"author": "ANDREW 4",
"rating": [4.7],
"reviews": [{good:4, excellent: 12}]
}
]
}
];
現(xiàn)在回顧上一節(jié)的問(wèn)題:獲取含有title和author字段且評(píng)級(jí)高于4.5的圖書(shū)。
map(apressBooks,(book)=>{
return book.bookDetails
})
得到如下輸出

如上圖所示,map函數(shù)返回的數(shù)據(jù)包含了數(shù)組中的數(shù)組,如果把上面的數(shù)據(jù)傳給filter將會(huì)遇到問(wèn)題,因?yàn)閒ilter不能在嵌套數(shù)組上運(yùn)行。
我們定義一個(gè)concatAll函數(shù)把所有嵌套數(shù)組連接到一個(gè)數(shù)組中,也可稱(chēng)concatAll為flatten方法(嵌套數(shù)組平鋪)。concatAll的主要目的是將嵌套數(shù)組轉(zhuǎn)換為非嵌套的單一數(shù)組。
const concatAll = (array) => {
let results = [];
for(const value of array){
results.push.apply(results,value) //重點(diǎn)??!
}
return results;
}
使用js的apply方法,將push的上下文設(shè)置為results
concatAll(map(apressBooks,(book)=>{
return book.bookDetails
}))
返回了我們期望的結(jié)果(數(shù)組平鋪)

轉(zhuǎn)換為非嵌套的單一數(shù)組后就可以繼續(xù)使用filter啦
filter(
concatAll(map(apressBooks,(book)=>{
return book.bookDetails
})),(book) => (book.rating[0] > 4.5)
)
返回結(jié)果

flatten嵌套數(shù)組扁平化
let arr = [[1,2,[3,4]],[4,5],77]
遍歷每一項(xiàng),如果仍是數(shù)組的話就遞歸調(diào)用flatten,并將結(jié)果與result concat一下。如果不是數(shù)組就直接push該項(xiàng)到result。
function flatten(array){
var result = [];
var toStr = Object.prototype.toString;
for(var i=0;i<array.length;i++){
var element = array[i];
if(toStr.call(element) === "[object Array]"){ //Array.isArray(element) === true
result = result.concat(flatten(element)); //[...result,...flatten(element)]
}
else{
result.push(element);
}
}
return result;
}
let results = flatten(arr)
5.3 reduce函數(shù)
reduce為保持Javascript閉包的能力所設(shè)計(jì)。
先來(lái)看一個(gè)數(shù)組求和問(wèn)題:
let useless = [2,5,6,1,10]
let result = 0;
forEach(useless,value=>{
result+=value;
})
console.log(result) //24
對(duì)于上面的問(wèn)題,我們將數(shù)組歸約為一個(gè)單一的值,從一個(gè)累加器開(kāi)始(result),在遍歷數(shù)組時(shí)使用它存儲(chǔ)求和結(jié)果。
歸約數(shù)組:設(shè)置累加器并遍歷數(shù)組(記住累加器的上一個(gè)值)以生成一個(gè)單一元素的過(guò)程稱(chēng)為歸約數(shù)組。
我們將這種歸約操作抽象成reduce函數(shù)。
reduce函數(shù)的第一個(gè)實(shí)現(xiàn)
const reduce = (array,fn)=>{
let accumlator = 0;
for(const value of array){
accumlator = fn(accumlator,value);
}
return [accumlator]
}
reduce(useless,(acc,val)=>acc+val) //[24]
但如果我們要求給定數(shù)組的乘積,reduce函數(shù)會(huì)執(zhí)行失敗,主要是因?yàn)槲覀兪褂昧死奂悠鞯闹?。
我們修改reduce函數(shù),讓它接受一個(gè)為累加器設(shè)置初始值的參數(shù)。
如果沒(méi)有傳遞initialValue時(shí),則以數(shù)組的第一個(gè)元素作為累加器的值。
const reduce = (array,fn,initialValue)=>{
let accumlator;
if(initialValue != undefined)
accumlator = initialValue;
else
accumlator = array[0];
//當(dāng)initialValue未定義時(shí),我們需要從第二個(gè)元素開(kāi)始循環(huán)數(shù)組
if(initialValue === undefined){
for(let i=1; i<array.length;i++){
accumlator = fn(accumlator,array[i])
}
}else{//如果initialValue由調(diào)用者傳入,我們就需要遍歷整個(gè)數(shù)組。
for(const value of array){
accumlator = fn(accumlator,value);
}
}
return [accumlator]
}
嘗試通過(guò)reduce函數(shù)解決乘積問(wèn)題
let useless = [2,5,6,1,10]
reduce(useless,(acc,val)=>acc*val,1) //[600]
reduce使用舉例
從apressBooks中統(tǒng)計(jì)評(píng)價(jià)為good和excellent的數(shù)量。->使用reduce
由于apressBooks包含數(shù)組中的數(shù)組,先需要使用concatAll把它轉(zhuǎn)化為一個(gè)扁平的數(shù)組。
concatAll(map(apressBooks,(book)=>{
return book.bookDetails
}))
我們使用reduce解決該問(wèn)題。
let bookDetails = concatAll(map(apressBooks,(book)=>{
return book.bookDetails
}))
reduce(bookDetails,(acc,bookDetail)=>{
let goodReviews = bookDetail.reviews[0] != undefined ? bookDetail.reviews[0].good : 0
let excellentReviews = bookDetail.reviews[0] != undefined ? bookDetail.reviews[0].good : 0
return {good:acc.good + goodReviews, excellent:acc.excellent + excellentReviews}
},{good:0,excellent:0})
在reduce函數(shù)體中,我們獲取good和excellent的評(píng)價(jià)詳情,將其存儲(chǔ)在相應(yīng)的變量中,名為goodReviews和excellentReviews。
完整代碼
5.4 zip數(shù)組
再回顧一下之前數(shù)據(jù)的結(jié)構(gòu),我們?cè)赼pressBooks的bookDetails中獲取reviews,并能輕松的操作它。
let apressBooks = [
{
name: "beginners",
bookDetails: [
{
"id": 111,
"title": "c# 6.0",
"author": "ANDREW 1",
"rating": [4],
"reviews": [{good:4, excellent: 12}]
},
{
"id": 222,
"title": "Machine Learning",
"author": "ANDREW 2",
"rating": [3],
"reviews": [{good:4, excellent: 12}]
}
]
},
{
name: "pro",
bookDetails: [
{
"id": 333,
"title": "Angularjs",
"author": "ANDREW 3",
"rating": [5],
"reviews": [{good:4, excellent: 12}]
},
{
"id": 444,
"title": "Pro ASP.NET",
"author": "ANDREW 4",
"rating": [4.7],
"reviews": [{good:4, excellent: 12}]
}
]
}
];
但是有時(shí)候數(shù)據(jù)可能被分離到不同部分了。
let apressBooks = [
{
name: "beginners",
bookDetails: [
{
"id": 111,
"title": "c# 6.0",
"author": "ANDREW 1",
"rating": [4]
},
{
"id": 222,
"title": "Machine Learning",
"author": "ANDREW 2",
"rating": [3],
"reviews": []
}
]
},
{
name: "pro",
bookDetails: [
{
"id": 333,
"title": "Angularjs",
"author": "ANDREW 3",
"rating": [5],
"reviews": []
},
{
"id": 444,
"title": "Pro ASP.NET",
"author": "ANDREW 4",
"rating": [4.7]
}
]
}
];
reviews被填充到一個(gè)單獨(dú)的數(shù)組中。
let reviewDetails = [
{
"id":111,
"reviews":[{good:4,excellent:12}]
},
{
"id":222,
"reviews":[]
},
{
"id":111,
"reviews":[]
},
{
"id":111,
"reviews":[{good:4,excellent:12}]
},
]
zip函數(shù)
const zip = (leftArr,rightArr,fn) => {
let index,results=[];
for(index=0;index<Math.min(leftArr.length,rightArr.length);index++){
results.push(fn(leftArr[index],rightArr[index]));
}
return results;
}
zip:我們只需要遍歷兩個(gè)給定的數(shù)組,由于要處理兩個(gè)數(shù)組詳情,就需要用 Math.min 獲取它們的最小長(zhǎng)度Math.min(leftArr.length, rightArr.length),一旦獲取了最小長(zhǎng)度,我們就能夠用當(dāng)前的leftArr值和rightArr值調(diào)用傳入的高階函數(shù)fn。
假設(shè)我們要把兩個(gè)數(shù)組的內(nèi)容相加,可以采用如下方式使用zip
zip([1,2,3],[4,5,6],(x,y)=>x+y)
繼續(xù)解決上一節(jié)的問(wèn)題:統(tǒng)計(jì)Apress出版物評(píng)價(jià)為good和excellent的總數(shù)。
我們接受bookDetails和reviewDetails數(shù)組,檢查兩個(gè)數(shù)組元素的id是否匹配,如果是,就從book中克隆出一個(gè)新的對(duì)象clone
//獲取bookDetails
let bookDetails = concatAll(map(apressBooks,(book)=>{
return book.bookDetails
}))
//zip results
let mergedBookDetails = zip(bookDetails, reviewDetails, (book, review)=>{
if(book.id === review.id){
let clone = Object.assign({},book)
clone.ratings = review //為clone添加一個(gè)ratings屬性,以review對(duì)象作為其值
return clone
}
})
注意:Object.assign(target, ...sources)
Object.assign() 方法用于將所有可枚舉屬性的值從一個(gè)或多個(gè)源對(duì)象復(fù)制到目標(biāo)對(duì)象。它將返回目標(biāo)對(duì)象。
let clone = Object.assign({},book)clone得到了一份book 對(duì)象的副本,clone指向了一個(gè)獨(dú)立的引用,為clone添加屬性或操作不會(huì)改變真實(shí)的book引用。

完整代碼-請(qǐng)copy到瀏覽器控制臺(tái)運(yùn)行
第六章:柯里化與偏應(yīng)用
6 一些術(shù)語(yǔ)
6.1 一元函數(shù)
只接受一個(gè)參數(shù)的函數(shù)稱(chēng)為一元(unary)函數(shù)。
const identity = (x) => x
6.2 二元函數(shù)
接受兩個(gè)參數(shù)的函數(shù)稱(chēng)為二元(binary)函數(shù)。
const add = (x,y) => x+y;
6.1 變參函數(shù)
指函數(shù)接受的參數(shù)數(shù)量是可變的。ES5中我們通過(guò)arguments來(lái)捕獲可變數(shù)量的參數(shù)。
function variadic(a){
console.log(a)
console.log(arguments)
}
調(diào)用
variadic(1,2,3)
1
[1,2,3]
ES6中我們使用擴(kuò)展運(yùn)算符,獲得可變參數(shù)
const variadic = (a,...variadic){
console.log(a)
console.log(variadic)
}
調(diào)用
variadic(1,2,3)
1
[2, 3]
6.2 柯里化
柯里化:把一個(gè)多參數(shù)函數(shù)轉(zhuǎn)換為一個(gè)嵌套的一元函數(shù)的過(guò)程。
看個(gè)例子,假設(shè)有一個(gè)名為add的函數(shù)
const add = (x,y)=>x+y;
我們會(huì)如此調(diào)用該函數(shù)add(1,1),得到結(jié)果2。下面是add函數(shù)的柯里化版本:
const addCurried = x => y => x+y;
如果我們用一個(gè)單一的參數(shù)調(diào)用addCurried,
addCurried(3)
它返回一個(gè)函數(shù),在其中x值通過(guò)閉包被捕獲,fn = y => 4+y ,因此可以用如下方式調(diào)用addCurried
addCurried(3)(4) //7
類(lèi)似的乘法函數(shù)
const curri = x=>y=>z=>x*y*z;
curri(2)(3)(4) //24
下面展示了如何把該處理過(guò)程轉(zhuǎn)換為一個(gè)名為curry的方法, curry方法將接收到的函數(shù)參數(shù)curry化
const curry = (binaryFn) => {
return function(firstArg){
return function(secondArg){
return binaryFn(firstArg,secondArg)
}
}
}
調(diào)用curry函數(shù),curry化add。
const add = (x,y)=>x+y;
let autoCurried = curry(add)
autoCurried(2)(3) //5
6.2.1 柯里化用例
假設(shè)我們要編寫(xiě)一個(gè)創(chuàng)建列表的函數(shù),創(chuàng)建列表tableOf2、tableOf3、tableOf4等。
const tableOf2 = (y) => 2*y;
const tableOf3 = (y) => 3*y;
const tableOf4 = (y) => 4*y;
現(xiàn)在可以把表格的概念概括為一個(gè)單獨(dú)的函數(shù)
const genericTable = (x,y) => x*y
我們將genericTable柯里化,用2填充tableOf2的第一個(gè)參數(shù),用3填充tableOf3的第一個(gè)參數(shù),用4填充tableOf4的第一個(gè)參數(shù)。
const tableOf2 = curry(genericTable)(2);
const tableOf3 = curry(genericTable)(3);
const tableOf4 = curry(genericTable)(4);
6.2.2 完整curry函數(shù)
添加規(guī)則,檢查如果傳入?yún)?shù)不是function,就會(huì)報(bào)錯(cuò)。
let curry = (fn) => {
if(typeof fn!=='function'){
throw Error('No function provided')
}
}
如果有人為柯里化函數(shù)提供了所有的參數(shù),就需要通過(guò)傳遞這些參數(shù)執(zhí)行真正的函數(shù),重點(diǎn)在于返回函數(shù)curriedFn是一個(gè)變參函數(shù)。
let curry = (fn) => {
if(typeof fn!=='function'){
throw Error('No function provided')
}
return function curriedFn(){ //返回函數(shù)是一個(gè)變參函數(shù)
return fn(...arguments)
}
//采用如下寫(xiě)法也ok
// return function curriedFn(...args){
// return fn(...args)
// }
}
如果我們有一個(gè)名為multiply的函數(shù):
const multiply = (x,y,z) => x*y*z;
可以通過(guò)如下方式調(diào)用,等價(jià)于multiply(1,2,3)
curry(multiply)(1,2,3) //6
下面回到把多參數(shù)函數(shù)轉(zhuǎn)換為嵌套的一元函數(shù)(柯里化的定義)
let curry = (fn) => {
if(typeof fn!=='function'){
throw Error('No function provided')
}
return function curriedFn(...args){ //args是一個(gè)數(shù)組
if(args.length < fn.length){ //檢查...args傳入的參數(shù)長(zhǎng)度是否小于函數(shù)參數(shù)列表的長(zhǎng)度
return function(){
args = [...args,...arguments]
return curriedFn(...args)
};
}
return fn(...args) //不小于,就和之前一樣調(diào)用整個(gè)函數(shù)
}
}
args.length < fn.length
檢查...args傳入的參數(shù)長(zhǎng)度是否小于函數(shù)參數(shù)列表的長(zhǎng)度,如果是,就進(jìn)入if代碼塊,如果不是就如之前一樣調(diào)用整個(gè)函數(shù)。
args = [...args,...arguments]用來(lái)連接一次傳入的參數(shù),把他們合并進(jìn)args,并遞歸調(diào)用curriedFn。由于我們將所有傳入的參數(shù) 組合并遞歸地調(diào)用,再下一次調(diào)用中將會(huì)遇到某一個(gè)時(shí)刻if(args.length < fn.length)條件失敗,說(shuō)明這時(shí)args存放的參數(shù)列表的長(zhǎng)度和函數(shù)參數(shù)的長(zhǎng)度相等,程序就會(huì)被調(diào)用 fn(...args)。
調(diào)用
const multiply = (x,y,z) => x*y*z;
curry(multiply)(1)(2)(3) //6
6.2.3 日志函數(shù) —— 柯里化的應(yīng)用
開(kāi)發(fā)者在寫(xiě)代碼時(shí)候會(huì)在應(yīng)用的不同階段編寫(xiě)很多日志。我們編寫(xiě)如下日志函數(shù)。
6.3 柯里化實(shí)戰(zhàn)
6.3.1 在數(shù)組內(nèi)容中查找數(shù)字
在數(shù)組中查找數(shù)字,返回包含數(shù)字的數(shù)組內(nèi)容。
無(wú)需柯里化時(shí),我們可以如下實(shí)現(xiàn)。
["js","number1"].filter(function(e){
return /[0-9]+/.test(e) //["number1"]
})
采用柯里化的filter函數(shù)
let filter = curry((fn,ary)=>{
return ary.filter(fn)
})
filter(function(str){
return /[0-9]+/.test(str);
})(["js","number1"]) //["number1"]
6.3.2 求數(shù)組的平方
前幾章中,我們使用map函數(shù)傳入一個(gè)平凡函數(shù)來(lái)解決問(wèn)題,此處可以通過(guò)curry函數(shù)以另一種方式解決該問(wèn)題。
let map = curry(function(f,ary){
return ary.map(f)
})
map(x=>x*x)([1,2,3]) //[1, 4, 9]
6.4 數(shù)據(jù)流
我們?cè)O(shè)計(jì)的柯里化函數(shù)總在最后接受數(shù)組,這是有意而為之。如果我們希望最后接受的參數(shù)是位于參數(shù)列表的中間某位置呢?curry就幫不了我們了。
6.4.1偏應(yīng)用
偏應(yīng)用:部分地應(yīng)用函數(shù)參數(shù)。有時(shí)填充函數(shù)的前兩個(gè)參數(shù)和最后一個(gè)參數(shù)會(huì)使中間的參數(shù)處于一種未知狀態(tài),這正是偏應(yīng)用發(fā)揮作用的地方,將未知狀態(tài)的參數(shù)填充為undefined,之后填入其他參數(shù)調(diào)用函數(shù)。
setTimeout(()=>console.log("Do X task"),10)
setTimeout(()=>console.log("Do Y task"),10)
我們?yōu)槊恳粋€(gè)setTimeout函數(shù)都傳入了10,我們希望把10作為常量,在代碼中把它隱藏。curry函數(shù)并不能幫我們解決這個(gè)問(wèn)題,原因是curry函數(shù)應(yīng)用參數(shù)列表的順序是從最左到最右。
一個(gè)方案是把setTimeout封裝一下,如此函數(shù)參數(shù)就會(huì)變成最右邊的一個(gè)。
const setTimeoutWrapper = (time,fn)=>{
setTimeout(fn,time);
}
然后就能通過(guò)curry函數(shù)來(lái)實(shí)現(xiàn)一個(gè)10ms的延遲了
const delayTenMs = curry(setTimeoutWrapper)(10)
delayTenMs(()=>console.log("Do X task"))
delayTenMs(()=>console.log("Do Y task"))
程序?qū)⒁晕覀冃枰姆绞竭\(yùn)行,但問(wèn)題是創(chuàng)建了setTimeoutWrapper這個(gè)封裝器,這是一種開(kāi)銷(xiāo)。
6.4.2 實(shí)現(xiàn)偏函數(shù)(適用于任何含有多個(gè)參數(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)
}
}
使用該偏函數(shù)
let delayTenMs = partial(setTimeout,undefined,10);
delayTenMs(()=>console.log("Do Y task"))
說(shuō)明:
我們調(diào)用
partial(setTimeout,undefined,10);
這將產(chǎn)生
let args = partialArgs = [undefined,10]返回函數(shù)將記住args的值(閉包)
返回函數(shù)非常簡(jiǎn)單,它接受一個(gè)名為fullArguments的參數(shù)。所以傳入()=>console.log("Do Y task")作為參數(shù),
在for循環(huán)中我們執(zhí)行遍歷并為函數(shù)創(chuàng)建必需的參數(shù)數(shù)組
if(args[i]===undefined){
args[i] = fullArguments[arg++];
}
從i=0開(kāi)始,
返回函數(shù)將記住args的值,返回函數(shù)非常簡(jiǎn)單,它接受一個(gè)名為fullArguments的參數(shù)。所以傳入
fullArguments = [()=>console.log("Do Y task")]
在if循環(huán)內(nèi)
args[0]===undefined=>true
args[0]=()=>console.log("Do Y task")
如此args就變成
[()=>console.log("Do Y task"),10]
可以看出,args指向我們期望的setTimeout函數(shù)調(diào)用所需的數(shù)組,一旦在args中有了必要的參數(shù),就可以通過(guò)fn.apply(null,args)調(diào)用函數(shù)了。
partial應(yīng)用
注意,我們可以將partial應(yīng)用于任何含有多個(gè)參數(shù)的函數(shù),看下面的例子。js中使用JSON.stringify() 方法將一個(gè)JavaScript值(對(duì)象或者數(shù)組)轉(zhuǎn)換為一個(gè) JSON字符串。
JSON.stringify(value[, replacer[, space]])
value:
必需, 要轉(zhuǎn)換的 JavaScript 值(通常為對(duì)象或數(shù)組)。
replacer:
可選。用于轉(zhuǎn)換結(jié)果的函數(shù)或數(shù)組。
如果 replacer 為函數(shù),則 JSON.stringify 將調(diào)用該函數(shù),并傳入每個(gè)成員的鍵和值。使用返回值而不是原始值。如果此函數(shù)返回 undefined,則排除成員。根對(duì)象的鍵是一個(gè)空字符串:""。
如果 replacer 是一個(gè)數(shù)組,則僅轉(zhuǎn)換該數(shù)組中具有鍵值的成員。成員的轉(zhuǎn)換順序與鍵在數(shù)組中的順序一樣。
space:
可選,文本添加縮進(jìn)、空格和換行符,如果 space 是一個(gè)數(shù)字,則返回值文本在每個(gè)級(jí)別縮進(jìn)指定數(shù)目的空格,如果 space 大于 10,則文本縮進(jìn) 10 個(gè)空格。space 也可以使用非數(shù)字,如:\t。
我們調(diào)用下面的函數(shù)做JSON的美化輸出。
let obj = {obj:"bar",bar:"foo"}
JSON.stringify(obj,null,2);
輸出:
"{
"obj": "bar",
"bar": "foo"
}"
可以看到stringify調(diào)用的最后兩個(gè)參數(shù)總是相同的“null,2”,我們可以用partial移除樣板代碼
let prettyPrintJson = partial(JSON.stringify, undefined, null, 2)
prettyPrintJson({obj:"bar",bar:"foo"})
輸出:
"{
"obj": "bar",
"bar": "foo"
}"
該偏函數(shù)的小bug:
如果我們使用一個(gè)不同的參數(shù)再次調(diào)用prettyPrintJson,它將總是給出第一次調(diào)用的結(jié)果。
prettyPrintJson({obj:"bar",bar:"foo222"})
輸出:總是給出第一次調(diào)用的結(jié)果
"{
"obj": "bar",
"bar": "foo"
}"
因?yàn)槲覀兺ㄟ^(guò)參數(shù)替換undefined值的方式修改partialArgs,而數(shù)組傳遞的是引用。
第七章:組合與管道(compose/pipe)
7.1 組合的概念
函數(shù)式組合:將多個(gè)函數(shù)組合在一起以便能構(gòu)建出一個(gè)新函數(shù)。
Unix的理念
1.每個(gè)程序只做好一件事情。
2.每個(gè)程序的輸出應(yīng)該是另一個(gè)尚不可知的程序的輸入。
Unix管道符號(hào)|
使用Unix管道符號(hào)|,就可以將左側(cè)的函數(shù)輸出作為右側(cè)函數(shù)的輸入。
如果想計(jì)算單詞word在給定文本文件中的出現(xiàn)次數(shù),該如何實(shí)現(xiàn)呢?
cat test.txt | grep 'world' | wc
cat用于在控制臺(tái)現(xiàn)實(shí)文本文件的內(nèi)容,它接受一個(gè)參數(shù)(文件位置)
grep在給定的文本中搜索內(nèi)容
wc計(jì)算單詞在給定文本中的數(shù)量
7.2 compose函數(shù)
本節(jié)創(chuàng)建第一個(gè)compose函數(shù),它需要接收一個(gè)函數(shù)的輸出,并將其作為輸入傳遞給另外一個(gè)函數(shù)。
const compose = (a, b)=>(c)=>a(b(c))
compose 接收函數(shù)a 、b作為輸入,并返回一個(gè)接收參數(shù)c的函數(shù)。當(dāng)用c調(diào)用返回函數(shù)時(shí),它將用輸入c調(diào)用函數(shù)b,b的輸出作為a的輸入,這就是compose函數(shù)的定義。
注意:函數(shù)的調(diào)用方向是從右至左的。
7.3 應(yīng)用compose函數(shù)
例子1:對(duì)一個(gè)給定的數(shù)字四舍五入求和。
let data = parseFloat("3.56")
let number = Math.round(data) //4
下面通過(guò)compose函數(shù)解決該問(wèn)題:
const compose = (a, b)=>(c)=>a(b(c))
let number = compose(Math.round, parseFloat)
number("3.56") //4
以上就是函數(shù)式組合,我們將兩個(gè)函數(shù)(Math.round、parseFloat)組合在一起以便能構(gòu)造出一個(gè)新函數(shù),注意:Math.round和parseFloat知道調(diào)用number函數(shù)時(shí)才會(huì)執(zhí)行。
例子2:計(jì)算一個(gè)字符串中單詞的數(shù)量
已有以下兩個(gè)函數(shù):
let splitIntoSpaces = (str) => str.split(" ")
let count = (array) => array.length;
如果想用這兩個(gè)函數(shù)構(gòu)建一個(gè)新函數(shù),計(jì)算一個(gè)字符串中單詞的數(shù)量。
const countWords = compose(count, splitIntoSpaces)
調(diào)用
countWords("hello what's your name") // 4
7.3.1 引入curry和partial
以上的例子中,僅當(dāng)函數(shù)接收一個(gè)參數(shù)時(shí),我們才能將兩個(gè)函數(shù)組合。但還存在多參數(shù)函數(shù)的情況,我們可以通過(guò)curry和partial函數(shù)來(lái)實(shí)現(xiàn)。
5.2中,我們通過(guò)以下寫(xiě)法從apressBooks中獲取含有title和author對(duì)象且評(píng)級(jí)高于4.5的對(duì)象。
map(filter(apressBooks, (book)=>book.rating[0]>4.5),(book)=>{
return {title:book.title,author:book.author}
})
本節(jié)使用compose函數(shù)將map和filter組合起來(lái)。
compose只能組合接受一個(gè)參數(shù)的函數(shù),但是map和filter都接受兩個(gè)參數(shù)map(array,fn) filter(array,fn)(數(shù)組,操作數(shù)組的函數(shù)),不能直接將他們組合。我們使用partial函數(shù)部分地應(yīng)用map和filter的第二個(gè)參數(shù)。
我們定義了過(guò)濾圖書(shū)的小函數(shù)filterGoodBooks和投影函數(shù)projectTitleAndAuthor
let filterGoodBooks = (book)=>book.rating[0]>4.5;
let projectTitleAndAuthor = (book)=>{return {title:book.title, author:book.author}}
現(xiàn)在使用compose和partial實(shí)現(xiàn)
let queryGoodBooks = partial(filter, undefined, filterGoodBooks);
let mapTitleAndAuthor = partial(map, undefined, projectTitleAndAuthor);
let titleAndAuthorForGoodBooks = compose(mapTitleAndAuthor, queryGoodBooks)
使用
titleAndAuthorForGoodBooks(apressBooks)
輸出:
0: {title: "Angularjs", author: "ANDREW JKDKS"}
1: {title: "Pro ASP.NET", author: "ANDREW JKDKS"}
本例子使用partial和compose解決問(wèn)題,也可以用curry來(lái)做同樣的事情。
提示:顛倒map和filter的參數(shù)順序。
const mapWrap = (fn,array)=>{
return map(array,fn)
}
7.3.2 組合多個(gè)函數(shù)
當(dāng)前的compose只能組合兩個(gè)給定的函數(shù),我們重寫(xiě)compose函數(shù),使它能組合三個(gè)、四個(gè)、更多函數(shù)。
const compose = (...fns) =>
(value) =>
reduce(fns.reverse(), (acc, fn) => fn(acc), value)
reduce用于把數(shù)組歸約為一個(gè)單一的值,例如求給定數(shù)組的元素乘積,累乘器初始值為1
reduce([1,2,3,4],(acc,val)=>acc*val,1) //24
此處通過(guò)fns.reverse()反轉(zhuǎn)函數(shù)數(shù)組,(acc, fn) => fn(acc)以傳入的acc為參數(shù)依次調(diào)用每一個(gè)函數(shù)。累加器的初始值是value變量,它作為函數(shù)的第一個(gè)輸入。
上一節(jié)中,我們組合了一個(gè)函數(shù)用于計(jì)算給定字符串的單詞數(shù)。
let splitIntoSpaces = (str) => str.split(" ")
let count = (array) => array.length;
const countWords = compose(count, splitIntoSpaces)
countWords("hello what's your name") // 4
假設(shè)我們想知道給定字符串的單詞數(shù)是基數(shù)還是偶數(shù),而我們已經(jīng)有如下函數(shù)
let oddOrEven = (ip) => ip%2 == 0 ? "even" : "odd";
通過(guò)compose,將這三個(gè)函數(shù)組合起來(lái)
const oddOrEvenWords = compose(oddOrEven, count, splitIntoSpaces);
oddOrEvenWords("hello what's your name") // ["even"]
7.4 管道/序列
compose的數(shù)據(jù)流是從右至左的,最右側(cè)的函數(shù)會(huì)首先執(zhí)行,將數(shù)據(jù)傳遞給下一個(gè)函數(shù),以此類(lèi)推...最左側(cè)的函數(shù)最后執(zhí)行。
而當(dāng)我們進(jìn)行“|”操作時(shí),Unix命令的數(shù)據(jù)流總是從左至右的,本節(jié)中,我們將實(shí)現(xiàn)pipe,它和compose函數(shù)所做的事情相同,只不過(guò)交換了數(shù)據(jù)流方向。
管道/序列(pipeline/sequence):從左至右處理數(shù)據(jù)流的過(guò)程稱(chēng)為管道/序列。
7.4.1 實(shí)現(xiàn)pipe
pipe是compose的復(fù)制品,唯一修改的是數(shù)據(jù)流方向。
const pipe = (...fns) =>
(value) =>
reduce(fns, (acc, fn) => fn(acc), value)
此處沒(méi)有像compose一樣調(diào)用fns.reverse(),這意味著我們將按照原有順序執(zhí)行函數(shù)。
調(diào)用pipe函數(shù)。注意,我們改變了調(diào)用順序,先splitIntoSpaces, 中count, 最后oddOrEven。
const oddOrEvenWords = pipe(splitIntoSpaces, count, oddOrEven);
oddOrEvenWords("hello what's your name") // ["even"]
7.5 組合的優(yōu)勢(shì):結(jié)合律
函數(shù)式組合滿(mǎn)足結(jié)合律:
compose(compose(f,g),h) == compose(f,compose(g,h))
看一下上一節(jié)的例子
//compose(compose(f,g),h)
const oddOrEvenWord1 = compose(compose(oddOrEven, count), splitIntoSpaces);
oddOrEvenWord1("hello what's your name") //["even"]
//compose(f,compose(g,h))
const oddOrEvenWord2 = compose(oddOrEven, compose(count, splitIntoSpaces));
oddOrEvenWord2("hello what's your name") //["even"]
真正的好處:把函數(shù)組合到各自所需的compose函數(shù)中,
let countWords = compose(count, splitIntoSpaces)
let oddOrEvenWords = compose(oddOrEven, countWords)
or
let countOddOrEven= compose(oddOrEven, count)
let oddOrEvenWords = compose(countOddOrEven, splitIntoSpaces)
第八章:函子
函子:用一種純函數(shù)式的方式進(jìn)行錯(cuò)誤處理。
8.1.1 函子是容器
函子是一個(gè)實(shí)現(xiàn)了map(遍歷每個(gè)對(duì)象值的時(shí)候生成一個(gè)新對(duì)象)的普通對(duì)象(在其他語(yǔ)言中可能是一個(gè)類(lèi))。簡(jiǎn)而言之,函子是一個(gè)持有值的容器,能夠持有任何傳給它值,并允許使用當(dāng)前容器持有的值調(diào)用任何函數(shù)。
創(chuàng)建Container構(gòu)造函數(shù)
const Container = function(val){
this.value = val;
}
不使用箭頭函數(shù)的原因是箭頭函數(shù)不具備內(nèi)部方法Construct和prototype屬性,所以不能用new來(lái)創(chuàng)建一個(gè)新對(duì)象。
應(yīng)用Container
let testValue = new Container(3) //Container {value: 3}
let testObj = new Container({a:1}) //Container {value: {a: 1}}
let testArray = new Container([1,2]) //Container {value: [1,2]}
我們?yōu)镃ontainer創(chuàng)建一個(gè)of靜態(tài)工具方法,用以代替new關(guān)鍵詞使用
Container.of = function(value){
return new Container(value)
}
用of方法重寫(xiě)上面的代碼
testValue = Container.of(3)
testObj = Container.of(3)
testArray = Container.of([1,2])
注意:Container也可以包含嵌套的 Container
Container.of(Container.of(33))
輸出:
Container {
value: Container {
value: 33
}
}
8.1.2 函子實(shí)現(xiàn)了map方法
map方法允許我們使用當(dāng)前Container持有的值調(diào)用任何函數(shù)。
即map函數(shù)從Container中取出值,將傳入的函數(shù)作用于該值,再將結(jié)果放回Container。
Container.prototype.map = function(fn){
return Container.of(fn(this.value))
}
第十章:使用Generator
Generator是ES6中關(guān)于函數(shù)的新規(guī)范。它不是一種函數(shù)式編程技術(shù),但它是函數(shù)的一部分。
10.1 異步代碼及其問(wèn)題(回調(diào)地獄)
同步VS異步
同步:函數(shù)執(zhí)行時(shí)會(huì)阻塞調(diào)用者,并在執(zhí)行完后返回結(jié)果。
異步:在執(zhí)行時(shí)不會(huì)阻塞調(diào)用者,一旦執(zhí)行完畢就會(huì)返回結(jié)果。
處理Ajax請(qǐng)求時(shí)就是在處理異步調(diào)用。
同步函數(shù)
let sync = () =>{
//一些操作
//返回?cái)?shù)據(jù)
}
let sync2 = () =>{
//一些操作
//返回?cái)?shù)據(jù)
}
let sync3 = () =>{
//一些操作
//返回?cái)?shù)據(jù)
}
同步函數(shù)調(diào)用
result = sync()
result2 = sync2()
result3 = sync3()
異步函數(shù)
let async = (fn)=>{
//一些異步操作
//用異步操作調(diào)用回調(diào)
fn(/*結(jié)果數(shù)據(jù)*/)
}
let async2 = (fn)=>{
//一些異步操作
//用異步操作調(diào)用回調(diào)
fn(/*結(jié)果數(shù)據(jù)*/)
}
let async3 = (fn)=>{
//一些異步操作
//用異步操作調(diào)用回調(diào)
fn(/*結(jié)果數(shù)據(jù)*/)
}
異步函數(shù)調(diào)用
async(function(x){
async2(function(y){
async3(function(z){
...
})
})
})
10.2 Generator基礎(chǔ)
Generator是ES6規(guī)范的一部分,被捆綁在語(yǔ)言層面。
10.2.1創(chuàng)建Generator
function* gen(){
return 'first generator';
}
gen()
返回一個(gè)Generator原始類(lèi)型的實(shí)例

調(diào)用實(shí)例的next函數(shù),從該Generator實(shí)例中獲取值
gen().next()
輸出:
{value: "first generator", done: true}
gen().next().value
輸出:
"first generator"
10.2.2 Generator的注意事項(xiàng)
一:不能無(wú)限制地調(diào)用next從Generator中取值
let genResult = gen()
//第一次調(diào)用
genResult.next().value
輸出:"first generator"
//第二次調(diào)用
genResult.next().value
輸出:undefined
原因是Generator如同序列,一旦序列中的值被消費(fèi),你就不能再次消費(fèi)它。
本例中,genResult是一個(gè)帶有"first generator"值的序列,第一次調(diào)用next后,我們就已經(jīng)從序列中消費(fèi)了該值。
由于序列已為空,第二次調(diào)用它就會(huì)返回undefined。
為了能夠再次消費(fèi)該序列,方法是創(chuàng)建另一個(gè)Generator實(shí)例
let genResult = gen()
let genResult2 = gen()
//第一個(gè)序列
genResult.next().value
輸出:"first generator"
//第二個(gè)序列
genResult2.next().value
輸出:"first generator"
10.3.2 yield關(guān)鍵詞
來(lái)看一個(gè)簡(jiǎn)單的Generator序列
function* generatorSequence(){
yield 'first';
yield 'second';
yield 'third';
}
創(chuàng)建實(shí)例并調(diào)用
let genSequence = generatorSequence();
genSequence.next().value //"first"
genSequence.next().value //"second"
genSequence.next().value //"third"
yield讓Generator惰性的生成一個(gè)值的序列。(直到調(diào)用才會(huì)執(zhí)行)
yield使Generator函數(shù)暫停了執(zhí)行并將結(jié)果返回給調(diào)用者,并且它還準(zhǔn)確地記住了暫停的位置。下一次調(diào)用時(shí)就從中斷的地方恢復(fù)執(zhí)行。
10.2.4 done屬性
done是一個(gè)判斷Generator序列已經(jīng)被完全消費(fèi)的屬性。當(dāng)done為true時(shí)就應(yīng)該停止調(diào)用Generator實(shí)例的next。
let genSequence = generatorSequence();
genSequence.next() //{value: "first", done: false}
genSequence.next() //{value: "second", done: false}
genSequence.next() //{value: "third", done: false}
genSequence.next() //{value: undefined, done: true}
下面的for...of循環(huán)用于遍歷Generator
function* generatorSequence(){
yield 'first';
yield 'second';
yield 'third';
}
for(let value of generatorSequence()){
console.log(value) //first second third
}
10.2.5 向Generator傳遞數(shù)據(jù)
function* sayFullName(){
var firstName = yield;
var secondName = yield;
console.log(firstName+secondName)
}
let fullName = sayFullName();
fullName.next()
fullName.next('xiao ')
fullName.next('ming')
輸出:xiao ming
分析:第一次調(diào)用fullName.next()時(shí),代碼將返回并暫停于var firstName = yield; 第二次調(diào)用yield被'xiao '替換,暫停在var secondName = yield;,第三次調(diào)用yield被'ming'替換,不再有yield。
10.3使用Generator處理異步調(diào)用
簡(jiǎn)單的異步函數(shù)
let getDataOne = (cb) => {
setTimeout(function(){
//調(diào)用函數(shù)
cb('dummy data one')
},1000)
}
let getDataTwo = (cb) => {
setTimeout(function(){
//調(diào)用函數(shù)
cb('dummy data two')
},1000)
}
調(diào)用
getDataOne((data)=>console.log(data)) //1000毫秒之后打印dummy data one
getDataTwo((data)=>console.log(data)) //1000毫秒之后打印dummy data two
下面改造getDataOne和getDataTwo函數(shù),使其使用Generator實(shí)例而不是回調(diào)來(lái)傳送數(shù)據(jù)
let generator;
let getDataOne = () => {
setTimeout(function(){
//調(diào)用Generator,通過(guò)next傳遞數(shù)據(jù)
generator.next('dummy data one')
},1000)
}
let getDataTwo = () => {
setTimeout(function(){
//調(diào)用Generator,通過(guò)next傳遞數(shù)據(jù)
generator.next('dummy data two')
},1000)
}
將getDataOne和getDataTwo調(diào)用封裝到一個(gè)單獨(dú)的Generator函數(shù)中
function* main(){
let dataOne = yield getDataOne();
let dataTwo = yield getDataTwo();
console.log(dataOne)
console.log(dataTwo)
}
用之前聲明的generator變量為main創(chuàng)建一個(gè)Generator實(shí)例。該Generator實(shí)例被getDataOne和getDataTwo同時(shí)用于向其調(diào)用傳遞數(shù)據(jù)。generator.next()用于觸發(fā)整個(gè)過(guò)程。main 函數(shù)開(kāi)始執(zhí)行,并遇到了第一個(gè)yield:let dataOne = yield getDataOne();
generator = main()
generator.next()
console.log("first be printed")
輸出:
first be printed
1000毫秒之后打印
dummy data one
dummy data two
main代碼看上去是在同步的調(diào)用getDataOne和getDataTwo,但其實(shí)兩個(gè)調(diào)用都是異步的。
有一點(diǎn)需要注意:雖然yield使語(yǔ)句暫停了,但它不會(huì)讓調(diào)用者阻塞。
generator.next() //雖然Generator為異步代碼暫停了
console.log("first be printed") //console.log正常執(zhí)行,說(shuō)明generator.next不會(huì)阻塞執(zhí)行