第一章:
函數(shù)式編程主要基于數(shù)學函數(shù)和它的思想眨层。
1.1 函數(shù)與js方法:
函數(shù)是一段可以通過其名稱被調(diào)用的代碼,可以傳遞參數(shù)并返回值凿将。
方法是一段必須通過其名稱及其關(guān)聯(lián)對象的名稱被調(diào)用的代碼此叠。
//函數(shù)
var func = (a)=>{return a}
func(5) //用其名稱調(diào)用
//方法
var obj = {simple:(a)=>{return a}}
obj.simple(5) //用其名稱及其關(guān)聯(lián)對象調(diào)用
1.2 引用透明性
所有函數(shù)對于相同的輸入都將返回相同的值(函數(shù)只依賴參數(shù)的輸入,不依賴于其他全局數(shù)據(jù)仁讨,即函數(shù)內(nèi)部沒有全局引用)羽莺,這使并行代碼和緩存(用值直接替換函數(shù)的結(jié)果)成為可能。
1.3 命令式洞豁、聲明式與抽象
命令式主張告訴編譯器“如何”做
var array = [1,2,3]
for(let i=0; o<array.length;i++){
console.log(array[i])
}
聲明式告訴編譯器“做什么”盐固,如何做的部分(獲得數(shù)組長度,循環(huán)遍歷每一項)被抽象到高階函數(shù)中丈挟,forEach就是這樣一個內(nèi)置函數(shù)刁卜,本書中我們都將創(chuàng)建這樣的內(nèi)置函數(shù)。
var array = [1,2,3]
array.forEach(elememt=>console.log(elememt))
1.4 函數(shù)式編程的好處&純函數(shù)
好處就是編寫純函數(shù)曙咽。純函數(shù)是對相同輸入返回相同輸出的函數(shù)蛔趴,不依賴(包含)任何外部變量,所以也不會產(chǎn)生改變外部環(huán)境變量的副作用例朱。
1.5 并行代碼
純函數(shù)允許我們并行執(zhí)行代碼孝情,因為純函數(shù)不會改變它的環(huán)境,所以不需要擔心同步問題洒嗤。當然箫荡,js并沒有真正的多線程支持并行,但如果你的項目使用了webworker來模擬多線程并行執(zhí)行任務(wù)渔隶,這種時候就需要用純函數(shù)來代替非純函數(shù)羔挡。
let global = 'something';
let function1 = (input) => {
global = "somethingElse"
}
let function2 = ()=>{
if(global === 'something'){
//業(yè)務(wù)邏輯
}
}
如果我們要兩個線程并行執(zhí)行function1和function2,由于兩個函數(shù)都依賴全局變量global间唉,并行執(zhí)行就會引起不良的影響(兩個函數(shù)的執(zhí)行順序不同會有不同的結(jié)果)绞灼,現(xiàn)在把它們改為純函數(shù)。
let function1 = (input,global)=>{
global = "somethingElse"
}
let function2 = (global)=>{
if(global === "something"){
//業(yè)務(wù)邏輯
}
}
我們移動了global變量终吼,把它作為兩個函數(shù)的參數(shù)镀赌,使他們變成純函數(shù)。現(xiàn)在并行執(zhí)行不會有任何問題际跪,由于函數(shù)不依賴于外部環(huán)境變量商佛,不必擔心線程的執(zhí)行順序。
1.6 可緩存
根據(jù)純函數(shù)對于給定輸入總是返回相同的輸出姆打,我們可以緩存函數(shù)的輸出良姆,減少多次的輸入來反復調(diào)用函數(shù)。
var longRunningFnBookKeeper = {2:3,4:5...}
longRunningFnBookKeeper.hasOwnProperty(ip)?longRunningFnBookKeeper[ip]:longRunningFunction(ip)
1.7 管道與組合
純函數(shù)應(yīng)該被設(shè)計為:只做一件事幔戏。實現(xiàn)多個功能通過函數(shù)的組合來實現(xiàn)玛追。
UNIX/LINUX中,在一個文件中找到一個特定的名稱并統(tǒng)計它的出現(xiàn)次數(shù):
cat jsBook | grep -i "composing" | wc
組合不是命令行特有的,它是函數(shù)式編程的核心痊剖。
1.8 關(guān)于js
js是一門面對對象的語言韩玩,不是一種純函數(shù)語言,更像是一種多范式語言陆馁,但是非常適合函數(shù)式編程找颓。
第二章:js函數(shù)基礎(chǔ)
今天很多瀏覽器還不支持ES6,我們可以通過轉(zhuǎn)換編譯器babel叮贩,將ES6轉(zhuǎn)換為ES5代碼击狮。
可以看到,箭頭函數(shù)的this經(jīng)過編譯后為undefined益老,轉(zhuǎn)換后的代碼運行在嚴格模式下彪蓬,嚴格模式是js的受限變體。
"use strict"
a = 1 // -> Uncaught ReferenceError: a is not defined; 此處直接報錯
在函數(shù)內(nèi)部如果用var聲明變量和不用時有很大差別捺萌,用var聲明的是局部變量档冬,在函數(shù)外部訪問這個變量是訪問不到的,沒var聲明的是全局變量桃纯。在函數(shù)外部是可以訪問到的捣郊。
如果你不使用var命令指定,在全局狀態(tài)下定一個變量慈参。在嚴格模式下這段代碼會報錯,因為全局變量在js中非常有害刮萌。
第三章:高階函數(shù)
高階函數(shù)(HOC):
- 接收函數(shù)作為參數(shù)
- 返回函數(shù)作為輸出
- 接收函數(shù)作為參數(shù)且返回函數(shù)作為輸出
滿足以上三個之一的函數(shù)就是高階函數(shù)驮配。
3.1 理解數(shù)據(jù)
3.1.1 js中函數(shù)為一等公民:
因為函數(shù)也是js中的一種數(shù)據(jù)類型,可以被賦值給變量着茸,作為參數(shù)傳遞壮锻,也可被其他函數(shù)返回。
3.1.2 把一個函數(shù)存入變量
let fn = () => {} //fn就是一個指向函數(shù)數(shù)據(jù)類型的變量,即函數(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ù)的引用荧库,并沒有執(zhí)行函數(shù)
let crazy = () =>{ return String }
crazy() // String() { [native code] }
crazy()('HOC') // "HOC"
3.2 抽象和高階函數(shù)
高階函數(shù)就是定義抽象
3.2.1通過高階函數(shù)實現(xiàn)抽象
forEach實現(xiàn)遍歷數(shù)組
const forEach = (array,fn)=>{
for(let i=0;i<array.length;i++){
fn(array[i])
}
}
forEachObject實現(xiàn)遍歷對象
const forEachObject = (obj,fn)=>{
for(var property in obj){
if(obj.hasOwnProperty(properity){
fn(property,obj[property])
})
}
}
注意:forEach和forEachObject都是高階函數(shù)小槐,他們使開發(fā)者專注于任務(wù),而抽象出遍歷的部分丙唧。
unless函數(shù):如果predicate為false伟阔,則調(diào)用fn
const unless = (predicate,fn)=>{
if(!predicate)
fn()
}
查找一個列表中的偶數(shù)
forEach([1,2,3,4,6,7],(number)=>{
unless((number%2),()=>{
console.log(number,"is even")
})
})
如果我們操作的是一個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ù)
a.every(function(element, index, array))
every是所有函數(shù)的每個回調(diào)函數(shù)都返回true的時候才會返回true辣之,當遇到false的時候終止執(zhí)行,返回false皱炉。
a.some(function(element, index, array))
some函數(shù)是“存在”有一個回調(diào)函數(shù)返回true的時候終止執(zhí)行并返回true怀估,否則返回false
在空數(shù)組上調(diào)用every返回true,some返回false。
3.3.1 every函數(shù)
every函數(shù)接受兩個參數(shù):一個數(shù)組和一個函數(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ù)組元素的方法歧蕉,重寫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ù)接受兩個參數(shù):一個數(shù)組和一個函數(shù)。它使用傳入的函數(shù)檢查數(shù)組的所有元素是否為true, 只要有一個為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ù)是一個高階函數(shù)康铭,它接受一個函數(shù)作為參數(shù)惯退,該函數(shù)幫助sort函數(shù)決定排序邏輯, 是一個改變原數(shù)組的方法。
arr.sort([compareFunc])
compareFunc是可選的麻削,如果compareFunc未提供蒸痹,元素將被轉(zhuǎn)換為字符串并按Unicode編碼點順序排列。
compareFunc應(yīng)該實現(xiàn)下面的邏輯
function compareFunc(a,b){
if(根據(jù)某種排序標準a<b){
return -1
}
if(根據(jù)某種排序標準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
}
寫成以下也ok呛哟,按照age升序排列
function compareFunc(a,b){
return a.age>b.age
}
friends.sort(compareFunc)
如果要比較不同的屬性,我們需要重復編寫比較代碼扫责。下面新建一個sortBy函數(shù)榛鼎,允許用戶基于傳入的屬性對對象數(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ù)接受一個屬性冰返回另一個函數(shù)鳖孤,這個返回的函數(shù)就作為compareFunc傳遞給sort函數(shù)者娱,持有property參數(shù)值的返回函數(shù)之所以能夠運行是因為js支持閉包。
第四章:高階函數(shù)與閉包
4.1理解閉包
4.1.1什么是閉包
簡言之苏揣,閉包是一個內(nèi)部函數(shù)黄鳍,它是在一個函數(shù)內(nèi)部的函數(shù)。
function outer(){
function inner(){
}
}
函數(shù)inner稱為閉包函數(shù)平匈,閉包如此強大的原因在于它對作用域鏈的訪問框沟。
閉包有3個可以訪問的作用域:
1.閉包函數(shù)內(nèi)聲明的變量
2.對全局變量的訪問
3.對外部函數(shù)變量的訪問!T鎏俊H淘铩!
let global = 'global';//2
function outer(){
let outer = 'outer';
function inner(){
let a=5;//1
console.log(outer) //3.閉包能夠訪問外部函數(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
當執(zhí)行var closeureFn = fn(5)
時隙姿,函數(shù)innerFn被返回梅垄,js執(zhí)行引擎視innerFn為一個閉包,并相應(yīng)的設(shè)置了它的作用域输玷。3個作用域?qū)蛹壴趇nnerFn返回時都被設(shè)置了队丝。
如此,closeureFn()通過作用域鏈被調(diào)用時就記住了arg饲嗽、outer的值炭玫。
我們回到sortBy
const sortBy = (property)=>{
return (a,b) => {
return (a[property]<b[property])?-1:(a[property]>b[property])?1:0
}
}
當我們以如下形式調(diào)用時
sortBy('age')
發(fā)生下面的事情:
sortBy函數(shù)返回了一個接受兩個參數(shù)的新函數(shù),這個新函數(shù)就是一個閉包
(a,b)=>{/*實現(xiàn)*/}
根據(jù)閉包能訪問作用域?qū)蛹壍奶攸c貌虾,它能在它的上下文中持有property的值吞加,所以它將在合適并且需要的時候使用返回值。
4.2真實的高階函數(shù)
4.2.1 once:允許只運行一次給定的函數(shù)
這在開發(fā)過程中很常見,例如只想設(shè)置一次第三方庫衔憨,初始化一次支付設(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í)行兩個參數(shù)并返回第二個表達式的結(jié)果践图。
注意:once函數(shù)接受一個參數(shù)fn并通過調(diào)用fn的apply方法返回結(jié)果掺冠。我們聲明了done變量,返回的函數(shù)會形成一個覆蓋它的閉包作用域码党,檢查done是否為true德崭,如果是則返回undefined,
否則將done設(shè)為true揖盘,如此就阻止了下一次的執(zhí)行眉厨。
4.2.2 memoized
用于為每一個輸入存儲結(jié)果,以便于重用函數(shù)中的計算結(jié)果兽狭。
const memoized = (fn) => {
const lookupTable = {};
return (arg) => lookupTable[arg] || (lookupTable[arg]=fn(arg));
}
有一個名為lookupTable的局部變量憾股,它在返回函數(shù)的閉包上下文中。返回函數(shù)將接受一個參數(shù)并檢查它是否在lookupTable中箕慧。
如果在服球,就返回對應(yīng)的值,否則使用新的輸入作為key颠焦,fn(arg)的結(jié)果為value斩熊,更新lookupTable對象。
求函數(shù)的階乘(遞歸法)
var factorial = (n) => {
if(n===0){
return 1;
}
return n*factorial(n-1)
}
現(xiàn)在可以改為把factorial函數(shù)包裹進一個memoized函數(shù)來保留它的輸出(存儲結(jié)果法)
let factorial = memoized((n)=>{
if(n===0){
return 1;
}
return n*factorial(n-1)
})
它以同樣的方式運行伐庭,但是比之前快的多座享。
第五章:數(shù)組的函數(shù)式編程
我們使用數(shù)組來存儲、操作和查找數(shù)據(jù)似忧,以及轉(zhuǎn)換(投影)數(shù)據(jù)格式。本章中使用函數(shù)式編程來改進這些操作丈秩。
5.1 數(shù)組的函數(shù)式方法
本節(jié)創(chuàng)建的所有函數(shù)稱為投影函數(shù)盯捌,把函數(shù)應(yīng)用于一個值并創(chuàng)建一個新值的過程稱為投影。
5.1.1 map
首先來看遍歷數(shù)組的forEach方法
const forEach = (array,fn) => {
for(const value of array)
fn(value)
}
map函數(shù)的實現(xiàn)代碼如下
const map = (array,fn) => {
let results= [];
for(const value of array)
results.push(fn(value))
return results;
}
map和forEach非常類似蘑秽,區(qū)別是用一個新的數(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ù)組的內(nèi)容(例如獲取rating>4.5的圖書列表)肠牲,再轉(zhuǎn)換為一個新數(shù)組幼衰,因此我們需要一個類似map的函數(shù),它只需要在把結(jié)果放入數(shù)組前檢查一個條件缀雳。
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ù)渡嚣,因此它們總是對數(shù)組應(yīng)用轉(zhuǎn)換操作后再返回數(shù)據(jù),于是我們能夠連接filter和map(注意順序)來完成任務(wù)而不需要額外變量。
例如:從apressBooks中獲取含有title和author對象且評級高于4.5的對象识椰。
map(filter(apressBooks, (book)=>book.rating[0]>4.5),(book)=>{
return {title:book.title,author:book.author}
})
我們將后面的章節(jié)中國通過函數(shù)組合來完成同樣的事情绝葡。
concatAll
對apressBooks對象稍作修改,得到如下數(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é)的問題:獲取含有title和author字段且評級高于4.5的圖書腹鹉。
map(apressBooks,(book)=>{
return book.bookDetails
})
得到如下輸出
如上圖所示藏畅,map函數(shù)返回的數(shù)據(jù)包含了數(shù)組中的數(shù)組,如果把上面的數(shù)據(jù)傳給filter將會遇到問題功咒,因為filter不能在嵌套數(shù)組上運行愉阎。
我們定義一個concatAll函數(shù)把所有嵌套數(shù)組連接到一個數(shù)組中,也可稱concatAll為flatten方法(嵌套數(shù)組平鋪)力奋。concatAll的主要目的是將嵌套數(shù)組轉(zhuǎn)換為非嵌套的單一數(shù)組榜旦。
const concatAll = (array) => {
let results = [];
for(const value of array){
results.push.apply(results,value) //重點!刊侯!
}
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]
遍歷每一項,如果仍是數(shù)組的話就遞歸調(diào)用flatten滨彻,并將結(jié)果與result concat一下藕届。如果不是數(shù)組就直接push該項到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è)計亭饵。
先來看一個數(shù)組求和問題:
let useless = [2,5,6,1,10]
let result = 0;
forEach(useless,value=>{
result+=value;
})
console.log(result) //24
對于上面的問題休偶,我們將數(shù)組歸約為一個單一的值,從一個累加器開始(result)辜羊,在遍歷數(shù)組時使用它存儲求和結(jié)果踏兜。
歸約數(shù)組:設(shè)置累加器并遍歷數(shù)組(記住累加器的上一個值)以生成一個單一元素的過程稱為歸約數(shù)組。
我們將這種歸約操作抽象成reduce函數(shù)八秃。
reduce函數(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ù)會執(zhí)行失敗,主要是因為我們使用了累加器的值0昔驱。
我們修改reduce函數(shù)疹尾,讓它接受一個為累加器設(shè)置初始值的參數(shù)。
如果沒有傳遞initialValue時骤肛,則以數(shù)組的第一個元素作為累加器的值纳本。
const reduce = (array,fn,initialValue)=>{
let accumlator;
if(initialValue != undefined)
accumlator = initialValue;
else
accumlator = array[0];
//當initialValue未定義時,我們需要從第二個元素開始循環(huán)數(shù)組
if(initialValue === undefined){
for(let i=1; i<array.length;i++){
accumlator = fn(accumlator,array[i])
}
}else{//如果initialValue由調(diào)用者傳入腋颠,我們就需要遍歷整個數(shù)組繁成。
for(const value of array){
accumlator = fn(accumlator,value);
}
}
return [accumlator]
}
嘗試通過reduce函數(shù)解決乘積問題
let useless = [2,5,6,1,10]
reduce(useless,(acc,val)=>acc*val,1) //[600]
reduce使用舉例
從apressBooks中統(tǒng)計評價為good和excellent的數(shù)量。->使用reduce
由于apressBooks包含數(shù)組中的數(shù)組淑玫,先需要使用concatAll把它轉(zhuǎn)化為一個扁平的數(shù)組巾腕。
concatAll(map(apressBooks,(book)=>{
return book.bookDetails
}))
我們使用reduce解決該問題面睛。
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的評價詳情祠墅,將其存儲在相應(yīng)的變量中侮穿,名為goodReviews和excellentReviews。
完整代碼
5.4 zip數(shù)組
再回顧一下之前數(shù)據(jù)的結(jié)構(gòu)毁嗦,我們在apressBooks的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ù)據(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被填充到一個單獨的數(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:我們只需要遍歷兩個給定的數(shù)組,由于要處理兩個數(shù)組詳情腔长,就需要用 Math.min 獲取它們的最小長度Math.min(leftArr.length, rightArr.length)
袭祟,一旦獲取了最小長度,我們就能夠用當前的leftArr值和rightArr值調(diào)用傳入的高階函數(shù)fn捞附。
假設(shè)我們要把兩個數(shù)組的內(nèi)容相加巾乳,可以采用如下方式使用zip
zip([1,2,3],[4,5,6],(x,y)=>x+y)
繼續(xù)解決上一節(jié)的問題:統(tǒng)計Apress出版物評價為good和excellent的總數(shù)。
我們接受bookDetails和reviewDetails數(shù)組鸟召,檢查兩個數(shù)組元素的id是否匹配胆绊,如果是,就從book中克隆出一個新的對象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添加一個ratings屬性欧募,以review對象作為其值
return clone
}
})
注意:Object.assign(target, ...sources)
Object.assign() 方法用于將所有可枚舉屬性的值從一個或多個源對象復制到目標對象压状。它將返回目標對象。
let clone = Object.assign({},book)
clone得到了一份book 對象的副本跟继,clone指向了一個獨立的引用种冬,為clone添加屬性或操作不會改變真實的book引用。
第六章:柯里化與偏應(yīng)用
6 一些術(shù)語
6.1 一元函數(shù)
只接受一個參數(shù)的函數(shù)稱為一元(unary)函數(shù)舔糖。
const identity = (x) => x
6.2 二元函數(shù)
接受兩個參數(shù)的函數(shù)稱為二元(binary)函數(shù)娱两。
const add = (x,y) => x+y;
6.1 變參函數(shù)
指函數(shù)接受的參數(shù)數(shù)量是可變的。ES5中我們通過arguments來捕獲可變數(shù)量的參數(shù)金吗。
function variadic(a){
console.log(a)
console.log(arguments)
}
調(diào)用
variadic(1,2,3)
1
[1,2,3]
ES6中我們使用擴展運算符谷婆,獲得可變參數(shù)
const variadic = (a,...variadic){
console.log(a)
console.log(variadic)
}
調(diào)用
variadic(1,2,3)
1
[2, 3]
6.2 柯里化
柯里化:把一個多參數(shù)函數(shù)轉(zhuǎn)換為一個嵌套的一元函數(shù)的過程。
看個例子辽聊,假設(shè)有一個名為add的函數(shù)
const add = (x,y)=>x+y;
我們會如此調(diào)用該函數(shù)add(1,1),得到結(jié)果2期贫。下面是add函數(shù)的柯里化版本:
const addCurried = x => y => x+y;
如果我們用一個單一的參數(shù)調(diào)用addCurried跟匆,
addCurried(3)
它返回一個函數(shù),在其中x值通過閉包被捕獲,fn = y => 4+y
,因此可以用如下方式調(diào)用addCurried
addCurried(3)(4) //7
類似的乘法函數(shù)
const curri = x=>y=>z=>x*y*z;
curri(2)(3)(4) //24
下面展示了如何把該處理過程轉(zhuǎn)換為一個名為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è)我們要編寫一個創(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)在可以把表格的概念概括為一個單獨的函數(shù)
const genericTable = (x,y) => x*y
我們將genericTable柯里化泡徙,用2填充tableOf2的第一個參數(shù)橱鹏,用3填充tableOf3的第一個參數(shù),用4填充tableOf4的第一個參數(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,就會報錯礁竞。
let curry = (fn) => {
if(typeof fn!=='function'){
throw Error('No function provided')
}
}
如果有人為柯里化函數(shù)提供了所有的參數(shù)糖荒,就需要通過傳遞這些參數(shù)執(zhí)行真正的函數(shù),重點在于返回函數(shù)curriedFn是一個變參函數(shù)模捂。
let curry = (fn) => {
if(typeof fn!=='function'){
throw Error('No function provided')
}
return function curriedFn(){ //返回函數(shù)是一個變參函數(shù)
return fn(...arguments)
}
//采用如下寫法也ok
// return function curriedFn(...args){
// return fn(...args)
// }
}
如果我們有一個名為multiply的函數(shù):
const multiply = (x,y,z) => x*y*z;
可以通過如下方式調(diào)用捶朵,等價于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是一個數(shù)組
if(args.length < fn.length){ //檢查...args傳入的參數(shù)長度是否小于函數(shù)參數(shù)列表的長度
return function(){
args = [...args,...arguments]
return curriedFn(...args)
};
}
return fn(...args) //不小于,就和之前一樣調(diào)用整個函數(shù)
}
}
args.length < fn.length
檢查...args傳入的參數(shù)長度是否小于函數(shù)參數(shù)列表的長度狂男,如果是综看,就進入if代碼塊,如果不是就如之前一樣調(diào)用整個函數(shù)并淋。
args = [...args,...arguments]
用來連接一次傳入的參數(shù)寓搬,把他們合并進args,并遞歸調(diào)用curriedFn县耽。由于我們將所有傳入的參數(shù) 組合并遞歸地調(diào)用句喷,再下一次調(diào)用中將會遇到某一個時刻if(args.length < fn.length)
條件失敗,說明這時args存放的參數(shù)列表的長度和函數(shù)參數(shù)的長度相等兔毙,程序就會被調(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)用
開發(fā)者在寫代碼時候會在應(yīng)用的不同階段編寫很多日志。我們編寫如下日志函數(shù)澎剥。
6.3 柯里化實戰(zhàn)
6.3.1 在數(shù)組內(nèi)容中查找數(shù)字
在數(shù)組中查找數(shù)字锡溯,返回包含數(shù)字的數(shù)組內(nèi)容。
無需柯里化時哑姚,我們可以如下實現(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ù)傳入一個平凡函數(shù)來解決問題叙量,此處可以通過curry函數(shù)以另一種方式解決該問題倡蝙。
let map = curry(function(f,ary){
return ary.map(f)
})
map(x=>x*x)([1,2,3]) //[1, 4, 9]
6.4 數(shù)據(jù)流
我們設(shè)計的柯里化函數(shù)總在最后接受數(shù)組,這是有意而為之绞佩。如果我們希望最后接受的參數(shù)是位于參數(shù)列表的中間某位置呢寺鸥?curry就幫不了我們了猪钮。
6.4.1偏應(yīng)用
偏應(yīng)用:部分地應(yīng)用函數(shù)參數(shù)。有時填充函數(shù)的前兩個參數(shù)和最后一個參數(shù)會使中間的參數(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)槊恳粋€setTimeout函數(shù)都傳入了10扑馁,我們希望把10作為常量,在代碼中把它隱藏宰译。curry函數(shù)并不能幫我們解決這個問題檐蚜,原因是curry函數(shù)應(yīng)用參數(shù)列表的順序是從最左到最右。
一個方案是把setTimeout封裝一下沿侈,如此函數(shù)參數(shù)就會變成最右邊的一個闯第。
const setTimeoutWrapper = (time,fn)=>{
setTimeout(fn,time);
}
然后就能通過curry函數(shù)來實現(xiàn)一個10ms的延遲了
const delayTenMs = curry(setTimeoutWrapper)(10)
delayTenMs(()=>console.log("Do X task"))
delayTenMs(()=>console.log("Do Y task"))
程序?qū)⒁晕覀冃枰姆绞竭\行,但問題是創(chuàng)建了setTimeoutWrapper這個封裝器缀拭,這是一種開銷咳短。
6.4.2 實現(xiàn)偏函數(shù)(適用于任何含有多個參數(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"))
說明:
我們調(diào)用
partial(setTimeout,undefined,10);
這將產(chǎn)生
let args = partialArgs = [undefined,10]
返回函數(shù)將記住args的值(閉包)
返回函數(shù)非常簡單,它接受一個名為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開始,
返回函數(shù)將記住args的值褐荷,返回函數(shù)非常簡單勾效,它接受一個名為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ù),就可以通過fn.apply(null,args)調(diào)用函數(shù)了其监。
partial應(yīng)用
注意萌腿,我們可以將partial應(yīng)用于任何含有多個參數(shù)的函數(shù),看下面的例子抖苦。js中使用JSON.stringify() 方法將一個JavaScript值(對象或者數(shù)組)轉(zhuǎn)換為一個 JSON字符串毁菱。
JSON.stringify(value[, replacer[, space]])
value:
必需, 要轉(zhuǎn)換的 JavaScript 值(通常為對象或數(shù)組)锌历。
replacer:
可選贮庞。用于轉(zhuǎn)換結(jié)果的函數(shù)或數(shù)組。
如果 replacer 為函數(shù)究西,則 JSON.stringify 將調(diào)用該函數(shù)窗慎,并傳入每個成員的鍵和值。使用返回值而不是原始值怔揩。如果此函數(shù)返回 undefined捉邢,則排除成員。根對象的鍵是一個空字符串:""商膊。
如果 replacer 是一個數(shù)組伏伐,則僅轉(zhuǎn)換該數(shù)組中具有鍵值的成員。成員的轉(zhuǎn)換順序與鍵在數(shù)組中的順序一樣晕拆。
space:
可選藐翎,文本添加縮進、空格和換行符实幕,如果 space 是一個數(shù)字吝镣,則返回值文本在每個級別縮進指定數(shù)目的空格,如果 space 大于 10昆庇,則文本縮進 10 個空格末贾。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)用的最后兩個參數(shù)總是相同的“null,2”,我們可以用partial移除樣板代碼
let prettyPrintJson = partial(JSON.stringify, undefined, null, 2)
prettyPrintJson({obj:"bar",bar:"foo"})
輸出:
"{
"obj": "bar",
"bar": "foo"
}"
該偏函數(shù)的小bug:
如果我們使用一個不同的參數(shù)再次調(diào)用prettyPrintJson表蝙,它將總是給出第一次調(diào)用的結(jié)果拴测。
prettyPrintJson({obj:"bar",bar:"foo222"})
輸出:總是給出第一次調(diào)用的結(jié)果
"{
"obj": "bar",
"bar": "foo"
}"
因為我們通過參數(shù)替換undefined值的方式修改partialArgs,而數(shù)組傳遞的是引用府蛇。
第七章:組合與管道(compose/pipe)
7.1 組合的概念
函數(shù)式組合:將多個函數(shù)組合在一起以便能構(gòu)建出一個新函數(shù)集索。
Unix的理念
1.每個程序只做好一件事情。
2.每個程序的輸出應(yīng)該是另一個尚不可知的程序的輸入汇跨。
Unix管道符號|
使用Unix管道符號|
务荆,就可以將左側(cè)的函數(shù)輸出作為右側(cè)函數(shù)的輸入。
如果想計算單詞word在給定文本文件中的出現(xiàn)次數(shù)扰法,該如何實現(xiàn)呢蛹含?
cat test.txt | grep 'world' | wc
cat用于在控制臺現(xiàn)實文本文件的內(nèi)容,它接受一個參數(shù)(文件位置)
grep在給定的文本中搜索內(nèi)容
wc計算單詞在給定文本中的數(shù)量
7.2 compose函數(shù)
本節(jié)創(chuàng)建第一個compose函數(shù)塞颁,它需要接收一個函數(shù)的輸出浦箱,并將其作為輸入傳遞給另外一個函數(shù)。
const compose = (a, b)=>(c)=>a(b(c))
compose 接收函數(shù)a 祠锣、b作為輸入酷窥,并返回一個接收參數(shù)c的函數(shù)。當用c調(diào)用返回函數(shù)時伴网,它將用輸入c調(diào)用函數(shù)b蓬推,b的輸出作為a的輸入,這就是compose函數(shù)的定義澡腾。
注意:函數(shù)的調(diào)用方向是從右至左的沸伏。
7.3 應(yīng)用compose函數(shù)
例子1:對一個給定的數(shù)字四舍五入求和糕珊。
let data = parseFloat("3.56")
let number = Math.round(data) //4
下面通過compose函數(shù)解決該問題:
const compose = (a, b)=>(c)=>a(b(c))
let number = compose(Math.round, parseFloat)
number("3.56") //4
以上就是函數(shù)式組合,我們將兩個函數(shù)(Math.round毅糟、parseFloat)組合在一起以便能構(gòu)造出一個新函數(shù)红选,注意:Math.round和parseFloat知道調(diào)用number函數(shù)時才會執(zhí)行。
例子2:計算一個字符串中單詞的數(shù)量
已有以下兩個函數(shù):
let splitIntoSpaces = (str) => str.split(" ")
let count = (array) => array.length;
如果想用這兩個函數(shù)構(gòu)建一個新函數(shù)姆另,計算一個字符串中單詞的數(shù)量喇肋。
const countWords = compose(count, splitIntoSpaces)
調(diào)用
countWords("hello what's your name") // 4
7.3.1 引入curry和partial
以上的例子中,僅當函數(shù)接收一個參數(shù)時迹辐,我們才能將兩個函數(shù)組合蝶防。但還存在多參數(shù)函數(shù)的情況,我們可以通過curry和partial函數(shù)來實現(xiàn)明吩。
5.2中间学,我們通過以下寫法從apressBooks中獲取含有title和author對象且評級高于4.5的對象。
map(filter(apressBooks, (book)=>book.rating[0]>4.5),(book)=>{
return {title:book.title,author:book.author}
})
本節(jié)使用compose函數(shù)將map和filter組合起來贺喝。
compose只能組合接受一個參數(shù)的函數(shù)菱鸥,但是map和filter都接受兩個參數(shù)map(array,fn)
filter(array,fn)
(數(shù)組,操作數(shù)組的函數(shù))躏鱼,不能直接將他們組合氮采。我們使用partial函數(shù)部分地應(yīng)用map和filter的第二個參數(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實現(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解決問題染苛,也可以用curry來做同樣的事情鹊漠。
提示:顛倒map和filter的參數(shù)順序。
const mapWrap = (fn,array)=>{
return map(array,fn)
}
7.3.2 組合多個函數(shù)
當前的compose只能組合兩個給定的函數(shù)茶行,我們重寫compose函數(shù)躯概,使它能組合三個、四個畔师、更多函數(shù)娶靡。
const compose = (...fns) =>
(value) =>
reduce(fns.reverse(), (acc, fn) => fn(acc), value)
reduce用于把數(shù)組歸約為一個單一的值,例如求給定數(shù)組的元素乘積看锉,累乘器初始值為1
reduce([1,2,3,4],(acc,val)=>acc*val,1) //24
此處通過fns.reverse()反轉(zhuǎn)函數(shù)數(shù)組姿锭,(acc, fn) => fn(acc)以傳入的acc為參數(shù)依次調(diào)用每一個函數(shù)。累加器的初始值是value變量伯铣,它作為函數(shù)的第一個輸入呻此。
上一節(jié)中,我們組合了一個函數(shù)用于計算給定字符串的單詞數(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";
通過compose,將這三個函數(shù)組合起來
const oddOrEvenWords = compose(oddOrEven, count, splitIntoSpaces);
oddOrEvenWords("hello what's your name") // ["even"]
7.4 管道/序列
compose的數(shù)據(jù)流是從右至左的,最右側(cè)的函數(shù)會首先執(zhí)行忿磅,將數(shù)據(jù)傳遞給下一個函數(shù)糯彬,以此類推...最左側(cè)的函數(shù)最后執(zhí)行。
而當我們進行“|”操作時葱她,Unix命令的數(shù)據(jù)流總是從左至右的情连,本節(jié)中,我們將實現(xiàn)pipe览效,它和compose函數(shù)所做的事情相同,只不過交換了數(shù)據(jù)流方向虫几。
管道/序列(pipeline/sequence):從左至右處理數(shù)據(jù)流的過程稱為管道/序列锤灿。
7.4.1 實現(xiàn)pipe
pipe是compose的復制品,唯一修改的是數(shù)據(jù)流方向辆脸。
const pipe = (...fns) =>
(value) =>
reduce(fns, (acc, fn) => fn(acc), value)
此處沒有像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)勢:結(jié)合律
函數(shù)式組合滿足結(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ù)式的方式進行錯誤處理。
8.1.1 函子是容器
函子是一個實現(xiàn)了map(遍歷每個對象值的時候生成一個新對象)的普通對象(在其他語言中可能是一個類)搀崭。簡而言之叨粘,函子是一個持有值的容器,能夠持有任何傳給它值瘤睹,并允許使用當前容器持有的值調(diào)用任何函數(shù)升敲。
創(chuàng)建Container構(gòu)造函數(shù)
const Container = function(val){
this.value = val;
}
不使用箭頭函數(shù)的原因是箭頭函數(shù)不具備內(nèi)部方法Construct和prototype屬性,所以不能用new來創(chuàng)建一個新對象轰传。
應(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)建一個of靜態(tài)工具方法驴党,用以代替new關(guān)鍵詞使用
Container.of = function(value){
return new Container(value)
}
用of方法重寫上面的代碼
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 函子實現(xiàn)了map方法
map方法允許我們使用當前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 異步代碼及其問題(回調(diào)地獄)
同步VS異步
同步:函數(shù)執(zhí)行時會阻塞調(diào)用者码俩,并在執(zhí)行完后返回結(jié)果度帮。
異步:在執(zhí)行時不會阻塞調(diào)用者,一旦執(zhí)行完畢就會返回結(jié)果。
處理Ajax請求時就是在處理異步調(diào)用笨篷。
同步函數(shù)
let sync = () =>{
//一些操作
//返回數(shù)據(jù)
}
let sync2 = () =>{
//一些操作
//返回數(shù)據(jù)
}
let sync3 = () =>{
//一些操作
//返回數(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ī)范的一部分懒鉴,被捆綁在語言層面。
10.2.1創(chuàng)建Generator
function* gen(){
return 'first generator';
}
gen()
返回一個Generator原始類型的實例
調(diào)用實例的next函數(shù)衰絮,從該Generator實例中獲取值
gen().next()
輸出:
{value: "first generator", done: true}
gen().next().value
輸出:
"first generator"
10.2.2 Generator的注意事項
一:不能無限制地調(diào)用next從Generator中取值
let genResult = gen()
//第一次調(diào)用
genResult.next().value
輸出:"first generator"
//第二次調(diào)用
genResult.next().value
輸出:undefined
原因是Generator如同序列沸久,一旦序列中的值被消費,你就不能再次消費它冕臭。
本例中腺晾,genResult是一個帶有"first generator"值的序列,第一次調(diào)用next后辜贵,我們就已經(jīng)從序列中消費了該值悯蝉。
由于序列已為空,第二次調(diào)用它就會返回undefined托慨。
為了能夠再次消費該序列鼻由,方法是創(chuàng)建另一個Generator實例
let genResult = gen()
let genResult2 = gen()
//第一個序列
genResult.next().value
輸出:"first generator"
//第二個序列
genResult2.next().value
輸出:"first generator"
10.3.2 yield關(guān)鍵詞
來看一個簡單的Generator序列
function* generatorSequence(){
yield 'first';
yield 'second';
yield 'third';
}
創(chuàng)建實例并調(diào)用
let genSequence = generatorSequence();
genSequence.next().value //"first"
genSequence.next().value //"second"
genSequence.next().value //"third"
yield讓Generator惰性的生成一個值的序列。(直到調(diào)用才會執(zhí)行)
yield使Generator函數(shù)暫停了執(zhí)行并將結(jié)果返回給調(diào)用者厚棵,并且它還準確地記住了暫停的位置蕉世。下一次調(diào)用時就從中斷的地方恢復執(zhí)行。
10.2.4 done屬性
done是一個判斷Generator序列已經(jīng)被完全消費的屬性婆硬。當done為true時就應(yīng)該停止調(diào)用Generator實例的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()
時,代碼將返回并暫停于var firstName = yield;
第二次調(diào)用yield
被'xiao '替換彬犯,暫停在var secondName = yield;
哈误,第三次調(diào)用yield被'ming'替換,不再有yield躏嚎。
10.3使用Generator處理異步調(diào)用
簡單的異步函數(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實例而不是回調(diào)來傳送數(shù)據(jù)
let generator;
let getDataOne = () => {
setTimeout(function(){
//調(diào)用Generator,通過next傳遞數(shù)據(jù)
generator.next('dummy data one')
},1000)
}
let getDataTwo = () => {
setTimeout(function(){
//調(diào)用Generator卢佣,通過next傳遞數(shù)據(jù)
generator.next('dummy data two')
},1000)
}
將getDataOne和getDataTwo調(diào)用封裝到一個單獨的Generator函數(shù)中
function* main(){
let dataOne = yield getDataOne();
let dataTwo = yield getDataTwo();
console.log(dataOne)
console.log(dataTwo)
}
用之前聲明的generator變量為main創(chuàng)建一個Generator實例重荠。該Generator實例被getDataOne和getDataTwo同時用于向其調(diào)用傳遞數(shù)據(jù)。generator.next()
用于觸發(fā)整個過程虚茶。main 函數(shù)開始執(zhí)行戈鲁,并遇到了第一個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,但其實兩個調(diào)用都是異步的嘹叫。
有一點需要注意:雖然yield使語句暫停了婆殿,但它不會讓調(diào)用者阻塞。
generator.next() //雖然Generator為異步代碼暫停了
console.log("first be printed") //console.log正常執(zhí)行罩扇,說明generator.next不會阻塞執(zhí)行