freeCodeCamp 旅途8 - 函數(shù)式編程

函數(shù)式編程

函數(shù)式編程是一種基于函數(shù)計算的軟件開發(fā)方法寸痢。像數(shù)學一樣,函數(shù)在編程中通過輸入產生輸出熙暴。函數(shù)式編程遵循幾個核心原則:

  • 獨立于程序狀態(tài)或全局變量卓缰,只依賴于傳遞給它們的參數(shù)進行計算
  • 限制更改程序狀態(tài),避免更改保存數(shù)據(jù)的全局對象
  • 對程序的副作用盡量小

學習函數(shù)式編程

函數(shù)式編程是一種解決方案簡單掌挚,功能獨立雨席,對作用域外沒有任何副作用的編程范式。INPUT -> PROCESS -> OUTPUT

函數(shù)式編程:
1)功能獨立——不依賴于程序的狀態(tài)(比如可能發(fā)生變化的全局變量)吠式;
2)純函數(shù)——同一個輸入永遠能得到同一個輸出陡厘;
3)有限的副作用——可以嚴格地限制函數(shù)外部對狀態(tài)的更改導致的狀態(tài)變化抽米。

const prepareTea = () => 'greenTea';
const getTea = (numOfCups) => {
  const teaCups = [];  
  for(let cups = 1; cups <= numOfCups; cups += 1) {
    const teaCup = prepareTea();
    teaCups.push(teaCup);
  }
  return teaCups;
};
const tea4TeamFCC = getTea(40); 
console.log(tea4TeamFCC);

了解函數(shù)式編程術語

Callbacks是被傳遞到另一個函數(shù)中調用的函數(shù)。例如在filter中糙置,回調函數(shù)告訴 JavaScript 以什么規(guī)則過濾數(shù)組云茸。
函數(shù)就像其他正常值一樣,可以賦值給變量谤饭、傳遞給另一個函數(shù)标捺,或從其它函數(shù)返回,這種函數(shù)叫做first class函數(shù)揉抵。在 JavaScript 中亡容,所有函數(shù)都是first class函數(shù)。
將函數(shù)為參數(shù)或返回值的函數(shù)叫做higher order函數(shù)冤今。
當函數(shù)傳遞給另一個函數(shù)或從另一個函數(shù)返回時闺兢,那些傳入或返回的函數(shù)可以叫做lambda

const prepareGreenTea = () => 'greenTea';  //  綠茶準備過程
const prepareBlackTea = () => 'blackTea';  //  紅茶準備過程
const getTea = (prepareTea, numOfCups) => {
  const teaCups = [];
  for(let cups = 1; cups <= numOfCups; cups += 1) {
    const teaCup = prepareTea();
    teaCups.push(teaCup);
  }
  return teaCups;
};
const tea4GreenTeamFCC = getTea(prepareGreenTea, 27); 
const tea4BlackTeamFCC = getTea(prepareBlackTea, 13); 
console.log(  tea4GreenTeamFCC,  tea4BlackTeamFCC);

了解使用命令式編程的危害

窗口對象由選項卡組成戏罢,通常會打開多個窗口屋谭。窗口對象中每個打開網(wǎng)站的標題都保存在一個數(shù)組中。在對瀏覽器進行了如打開新標簽帖汞、合并窗口、關閉標簽之類的操作后凑术,你需要輸出所有打開的標簽翩蘸。關掉的標簽將從數(shù)組中刪除,新打開的標簽(為簡單起見)則添加到數(shù)組的末尾淮逊。

var Window = function(tabs) { 
  this.tabs = tabs; // 我們將數(shù)組記錄在對象內部
}; // tabs 是窗口中打開網(wǎng)站的標題數(shù)組
Window.prototype.join = function (otherWindow) {
  this.tabs = this.tabs.concat(otherWindow.tabs);
  return this;
}; // 當兩個窗口合并成一個窗口時
Window.prototype.tabOpen = function (tab) {
  this.tabs.push('new tab'); // 打開一個新的標簽
  return this;
};  // 在末尾打開一個新標簽
Window.prototype.tabClose = function (index) {
  var tabsBeforeIndex = this.tabs.splice(0, index); // 獲取前面的標簽
  var tabsAfterIndex = this.tabs.splice(index); // 獲取后面的標簽
  this.tabs = tabsBeforeIndex.concat(tabsAfterIndex); // 拼接到一起
  return this;
 }; // 關閉一個標簽
// 創(chuàng)建三個瀏覽器窗口
var workWindow = new Window(['GMail', 'Inbox', 'Work mail', 'Docs', 'freeCodeCamp']); // 郵箱催首、文檔及其他與工作相關的網(wǎng)站
var socialWindow = new Window(['FB', 'Gitter', 'Reddit', 'Twitter', 'Medium']); // 社交網(wǎng)站
var videoWindow = new Window(['Netflix', 'YouTube', 'Vimeo', 'Vine']); // 娛樂網(wǎng)站
var finalTabs = socialWindow
                    .tabOpen() // 新開一個 cat memes 的標簽
                    .join(videoWindow.tabClose(2)) // 在娛樂網(wǎng)站關閉第三個標簽,加入數(shù)組
                    .join(workWindow.tabClose(1).tabOpen());
alert(finalTabs.tabs); // 執(zhí)行標簽打開泄鹏,關閉和其他操作

使用函數(shù)式編程避免突變和副作用:問題出在tabClose()函數(shù)里的splice郎任。splice修改了調用它的原始數(shù)組,所以第二次調用它時是基于修改后的數(shù)組备籽,才給出了意料之外的結果舶治。
函數(shù)式編程的核心原則之一是不改變任何東西。變化會導致錯誤车猬。如果一個函數(shù)不改變傳入的參數(shù)霉猛、全局變量等數(shù)據(jù),那么它造成問題的可能性就會小很多珠闰。
在函數(shù)式編程中惜浅,改變或變更叫做mutation,這種改變的結果叫做“副作用”(side effect)伏嗜。理想情況下坛悉,函數(shù)應該是不會產生任何副作用的pure function伐厌。

var fixedValue = 4;
function incrementer () {
  return fixedValue + 1;
}
var newValue = incrementer(); // 應等于 5
console.log(fixedValue); // 應打印 4

傳遞參數(shù)以避免函數(shù)中的外部依賴

函數(shù)式編程的另一個原則是:總是顯式聲明依賴關系。如果函數(shù)依賴于一個變量或對象裸影,那么將該變量或對象作為參數(shù)直接傳遞到函數(shù)中挣轨。

var fixedValue = 4;
function incrementer (value) {
  return value + 1;
}
var newValue = incrementer(fixedValue); // 應等于 5
console.log(fixedValue); // 應打印 4

在函數(shù)中重構全局變量

// 全局變量
var bookList = ["The Hound of the Baskervilles", "On The Electrodynamics of Moving Bodies", "Philosophi? Naturalis Principia Mathematica", "Disquisitiones Arithmeticae"];
function add (arr, bookName) {
  let newArr = [...arr];  
  newArr.push(bookName);  
  return newArr; 
}
function remove (arr, bookName) {
  let newArr = [...arr]; 
  if (newArr.indexOf(bookName) >= 0) {  
    newArr.splice(newArr.indexOf(bookName), 1); 
    return newArr; // Return the new array.
    }
}
var newBookList = add(bookList, 'A Brief History of Time');
var newerBookList = remove(bookList, 'On The Electrodynamics of Moving Bodies');
var newestBookList = remove(add(bookList, 'A Brief History of Time'), 'On The Electrodynamics of Moving Bodies');
console.log(bookList);
//  等價于
function add (list,bookName) {
  return [...list, bookName];
}
function remove (list,bookName) {
  if (list.indexOf(bookName) >= 0) {
    return list.filter((item) => item !== bookName);
    }
}

使用 map 方法從數(shù)組中提取數(shù)據(jù)

函數(shù)在 JavaScript 中被視為First Class Objects,它們可以像任何其他對象一樣使用空民。它們可以保存在變量中刃唐,存儲在對象中,也可以作為函數(shù)參數(shù)傳遞界轩。
map方法是迭代數(shù)組中每一項的方式之一画饥。在對每個元素應用回調函數(shù)后,它會創(chuàng)建一個新數(shù)組(不改變原來的數(shù)組)浊猾。

rating = watchList.map( (item) => ({"title":item["Title"], "rating":item["imdbRating"]}) );

在原型上實現(xiàn) map 方法:map是一個純函數(shù)抖甘,它的輸出僅取決于輸入的數(shù)組和作為參數(shù)傳入的回調函數(shù)。純函數(shù)可以改變其作用域內定義的局部變量葫慎,但我們最好不要這樣做衔彻。

var s = [23, 65, 98, 5];
Array.prototype.myMap = function(callback){
  var newArray = [];
  for(let i = 0; i < this.length; i++){    newArray.push(callback(this[i]));  }
//  等價于
//  this.forEach(a => newArray.push(callback(a)));
  return newArray;
};
var new_s = s.myMap(function(item){
  return item * 2;
});

使用 filter 方法從數(shù)組中提取數(shù)據(jù)

另一個有用的數(shù)組方法是filter()(即Array.prototype.filter())。filter方法會返回一個長度不大于原始數(shù)組的新數(shù)組偷办。
map一樣艰额,Filter不會改變原始數(shù)組,它接收一個回調函數(shù)椒涯,將回調內的邏輯應用于數(shù)組的每個元素柄沮。新數(shù)組包含根據(jù)回調函數(shù)內條件返回 true 的元素。

var filteredList = watchList.map(function(e) {
  return {title: e["Title"], rating: e["imdbRating"]}
}).filter((e) => e.rating >= 8);

在原型上實現(xiàn) filter 方法:

var s = [23, 65, 98, 5];
Array.prototype.myFilter = function(callback){
  var newArray = [];
  for (let i=0; i<this.length;i++){
    if(callback(this[i])=== true ){ newArray.push(this[i]);    }
  }
//   等價于
//    this.forEach(function(x) {    if (callback(x) == true) {  newArray.push(x);  }  });
  return newArray;
};
var new_s = s.myFilter(function(item){
  return item % 2 === 1;
});

使用 slice 方法返回數(shù)組的一部分

slice方法可以從已有數(shù)組中返回指定元素废岂。它接受兩個參數(shù)祖搓,第一個規(guī)定從何處開始選取,第二個規(guī)定從何處結束選群(不包括該元素)拯欧。如果沒有傳參,則默認為從數(shù)組的開頭開始到結尾結束财骨,這是復制整個數(shù)組的簡單方式镐作。slice返回一個新數(shù)組,不會修改原始數(shù)組隆箩。

var arr = ["Cat", "Dog", "Tiger", "Zebra"];
var newArray = arr.slice(1, 3);  // 將新數(shù)組設置為 ["Dog", "Tiger"]

使用 slice 而不是 splice 從數(shù)組中移除元素:JavaScript 提供了splice方法滑肉,它接收兩個參數(shù):從哪里開始刪除項目的索引,和要刪除的項目數(shù)摘仅。如果沒有提供第二個參數(shù)靶庙,默認情況下是移除到結尾的元素。但splice方法會改變調用它的原始數(shù)組。

var cities = ["Chicago", "Delhi", "Islamabad", "London", "Berlin"];
cities.splice(3, 1); // 返回 "London" 并將它從 cities 數(shù)組刪除
// cities 現(xiàn)在是 ["Chicago", "Delhi", "Islamabad", "Berlin"]

使用 concat 方法組合兩個數(shù)組

Concatenation意思是將元素連接到尾部六荒。同理护姆,JavaScript 為字符串和數(shù)組提供了concat方法。對數(shù)組來說掏击,在一個數(shù)組上調用concat方法卵皂,然后提供另一個數(shù)組作為參數(shù)添加到第一個數(shù)組末尾,返回一個新數(shù)組砚亭,不會改變任何一個原始數(shù)組灯变。

[1, 2, 3].concat([4, 5, 6]);   // 返回新數(shù)組 [1, 2, 3, 4, 5, 6]

使用 concat 而不是 push 將元素添加到數(shù)組的末尾:函數(shù)式編程就是創(chuàng)建和使用 non-mutating 函數(shù)。

var arr = [1, 2, 3];
arr.push([4, 5, 6]); // arr 變成了 [1, 2, 3, [4, 5, 6]]  不是函數(shù)式編程

使用 reduce 方法分析數(shù)據(jù)

reduce()(即Array.prototype.reduce())捅膘,是 JavaScript 所有數(shù)組操作中最通用的方法添祸。幾乎可以用reduce方法解決所有數(shù)組處理問題。
filtermap方法不支持對數(shù)組中兩個不同元素的交互寻仗。舉個例子刃泌,如果你想把數(shù)組中的元素拿來比較或者相加,用filtermap是做不到的署尤。
reduce方法允許更通用的數(shù)組處理方式耙替,而且filtermap方法都可以當作是reduce的特殊實現(xiàn)。

var averageRating = watchList.filter(x => x.Director === "Christopher Nolan").map(x => Number(x.imdbRating)).reduce((x1, x2) => x1 + x2) / watchList.filter(x => x.Director === "Christopher Nolan").length;

使用 sort 方法按字母順序給數(shù)組排序

在alphabeticalOrder函數(shù)中使用sort方法對arr中的元素按照字母順序排列:

function alphabeticalOrder(arr) {
  return arr.sort(function (a, b) {
   if (a < b) return -1
   else if (a > b) return 1
   else return 0
  });
}
alphabeticalOrder(["a", "d", "c", "a", "z", "g"]);

在不更改原始數(shù)組的前提下返回排序后的數(shù)組

concat返回一個新數(shù)組曹体,再用sort方法:

var globalArray = [5, 6, 3, 2, 9];
function nonMutatingSort(arr) {
  return [].concat(arr).sort(function(a,b){
    if(a<b) return -1
    else if(a>b) return 1
    else return 0
  });
}
nonMutatingSort(globalArray);

使用 split 方法將字符串拆分成數(shù)組

split方法用于把字符串分割成字符串數(shù)組俗扇,接收一個分隔符參數(shù),分隔符可以是用于分解字符串或正則表達式的字符箕别。

function splitify(str) {
  return str.split(/\W/);
}
splitify("Hello World,I-am code");

使用 join 方法將數(shù)組組合成字符串

join方法用來把數(shù)組中的所有元素放入一個字符串铜幽,并通過指定的分隔符參數(shù)進行分隔。

function sentensify(str) {
  return str.split(/\W/).join(" ");
}
sentensify("May-the-force-be-with-you");

應用函數(shù)式編程將字符串轉換為URL片段

var globalTitle = "Winter Is Coming";
function urlSlug(title) {
    return title.split(/\W/).filter((obj)=>{
        return obj !=='';
    }).join('-').toLowerCase();  
}
var winterComing = urlSlug(globalTitle); // 應為 "winter-is-coming"

使用 every 方法檢查數(shù)組中的每個元素是否符合條件

使用every方法檢查arr中是否所有元素都是正數(shù):

function checkPositive(arr) {
  return arr.every(function(a){
    return a > 0;
  });
}
checkPositive([1, 2, 3, -4, 5]);

使用 some 方法檢查數(shù)組中是否有元素是否符合條件

使用some方法檢查arr中是否所有元素都是正數(shù):

function checkPositive(arr) {
  return arr.some(function(a){
    return a > 0;
  });
}
checkPositive([1, 2, 3, -4, 5]);

函數(shù)柯里化

arity是函數(shù)所需的形參的數(shù)量究孕。函數(shù)Currying意思是把接受多個arity的函數(shù)變換成接受單一arity的函數(shù)啥酱。換句話說爹凹,就是重構函數(shù)讓它接收一個參數(shù)厨诸,然后返回接收下一個參數(shù)的函數(shù),依此類推禾酱。

function unCurried(x, y) {
  return x + y;
}
// 柯里化函數(shù)
function curried(x) {
  return function(y) {
    return x + y;
  }
}
curried(1)(2) // 返回 3

var funcForY = curried(1);
console.log(funcForY(2)); // 打印 3

function impartial(x, y, z) {
  return x + y + z;
}
var partialFn = impartial.bind(this, 1, 2);
partialFn(10); // 返回 13

function add(x) {
  return function(y){
    return function(z){
      return x + y + z;
    };
  };
}
add(10)(20)(30);
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末微酬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子颤陶,更是在濱河造成了極大的恐慌颗管,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滓走,死亡現(xiàn)場離奇詭異垦江,居然都是意外死亡,警方通過查閱死者的電腦和手機搅方,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門比吭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绽族,“玉大人,你說我怎么就攤上這事衩藤“陕” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵赏表,是天一觀的道長检诗。 經常有香客問我,道長瓢剿,這世上最難降的妖魔是什么逢慌? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮跋选,結果婚禮上涕癣,老公的妹妹穿的比我還像新娘。我一直安慰自己前标,他們只是感情好坠韩,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著炼列,像睡著了一般只搁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上俭尖,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天氢惋,我揣著相機與錄音,去河邊找鬼稽犁。 笑死焰望,一個胖子當著我的面吹牛,可吹牛的內容都是我干的已亥。 我是一名探鬼主播熊赖,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼虑椎!你這毒婦竟也來了震鹉?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤捆姜,失蹤者是張志新(化名)和其女友劉穎传趾,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泥技,經...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡浆兰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片簸呈。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡宽涌,死狀恐怖,靈堂內的尸體忽然破棺而出蝶棋,到底是詐尸還是另有隱情卸亮,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布玩裙,位于F島的核電站兼贸,受9級特大地震影響,放射性物質發(fā)生泄漏吃溅。R本人自食惡果不足惜溶诞,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望决侈。 院中可真熱鬧螺垢,春花似錦、人聲如沸赖歌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽庐冯。三九已至孽亲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間展父,已是汗流浹背返劲。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留栖茉,地道東北人篮绿。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像吕漂,于是被迫代替她去往敵國和親亲配。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內容