函數(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ù)組處理問題。
filter
和map
方法不支持對數(shù)組中兩個不同元素的交互寻仗。舉個例子刃泌,如果你想把數(shù)組中的元素拿來比較或者相加,用filter
和map
是做不到的署尤。
reduce
方法允許更通用的數(shù)組處理方式耙替,而且filter
和map
方法都可以當作是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);