原文:Higher Order Functions: Using Filter, Map and Reduce for More Maintainable Code
作者:Guido Schmitz
高階函式可以幫助你增強你的JavaScript,讓你的程式碼更具有宣告性。簡單來說,就是簡單,簡練,可讀。
知道什麼時候和怎樣使用高階函式是至關重要的。它們可以讓你的程式碼更容易理解和具有更好的可維護性。它們也可以讓你很輕鬆的進行函式間的組合。我們叫它複合函式,不過我不會在這裡進行詳細的介紹。在本文中,我將介紹JavaScript中三個最常用的高階函式:.filter()
,.map()
,.reduce
。
Filter
想象一下你正在編寫一段程式碼:有一個寫滿不同人資訊的列表,不過你想要過濾出一個大於等於18歲人的列表。
我們的列表看起來就像下面這樣:
const people = [
{ name: ‘John Doe’, age: 16 },
{ name: ‘Thomas Calls’, age: 19 },
{ name: ‘Liam Smith’, age: 20 },
{ name: ‘Jessy Pinkman’, age: 18 },
];複製程式碼
我們先來看看第一個高階函式是如何篩選出大於等於18歲人的栗子。為了簡潔,我將使用ES6標準中的箭頭函式。這是一種非常簡潔的定義函式的方式,可以讓我們不必再寫function
和return
,以及一些括號、大括號和分號。
const peopleAbove18 = (collection) => {
const results = [];
for (let i = 0; i < collection.length; i++) {
const person = collection[i];
if (person.age >= 18) {
results.push(person);
}
}
return results;
};複製程式碼
那現在如果我們想要篩選出18~20歲之間的人呢?
const peopleBetween18And20 = (collection) => {
const results = [];
for (let i = 0; i < collection.length; i++) {
const person = collection[i];
if (person.age >= 18 && person.age <= 20) {
results.push(person);
}
}
return results;
};複製程式碼
你可能已經意識到了這裡有許多重複的程式碼。我們可以抽象出一個通用的解決方案。這兩個函式有一些共同點。它們都在一個列表中進行迭代,並且在給定的條件下進行過濾。
"高階函式是一個將一個或多個函式作為引數的函式"——ClojureBridge
我們可以通過使用更具宣告性的.filter()
方法來改進我們之前的函式。
const peopleAbove18 = (collection) => {
return collection
.filter((person) => person.age >= 18);
}複製程式碼
太棒了!我們通過使用高階函式減少了許多額外的程式碼。同時也讓我們的程式碼更具可讀性。我們不在乎如何過濾東西,我們只是希望它被過濾。這篇文章稍後會介紹組合函式。
Map
讓我們拿著剛剛的人員名單和一個其中喜歡喝咖啡的人員名單。
const coffeeLovers = [‘John Doe’, ‘Liam Smith’, ‘Jessy Pinkman’];複製程式碼
用命令式的實現方式就像下面這樣:
const addCoffeeLoverValue = (collection) => {
const results = [];
for (let i = 0; i < collection.length; i++) {
const person = collection[i];
if (coffeeLovers.includes(person.name)) {
person.coffeeLover = true;
} else {
person.coffeeLover = false;
}
results.push(person);
}
return results;
};複製程式碼
我們可以利用.map()
來讓程式碼更具有宣告性:
const incrementAge = (collection) => {
return collection.map((person) => {
person.coffeeLover = coffeeLovers.includes(person.name);
return person;
});
};複製程式碼
再說一遍,.map()
是一個高階函式。它允許我們將一個函式作為引數傳遞。
Reduce
我敢打賭,當你知道什麼時候和怎樣使用它的時候,你會喜歡上這個函式。.reduce()
很酷,上面提到的的大部分函式都可以通過它來實現。
讓我們先舉一個簡單的栗子。我們想計算所有人年齡的和。當然了,我們還是會首先看看如何用命令式的方式實現。它基本上就是通過迴圈來增加總年齡變數。
const sumAge = (collection) => {
let num = 0;
collection.forEach((person) => {
num += person.age;
});
return num;
}複製程式碼
接下來是使用.reduce()
的宣告式方法:
const sumAge = (collection) => collection.reduce((sum, person) => {
return sum + person.age;
}, 0);複製程式碼
我們甚至可以使用.reduce()
來建立我們自己的.map()
和.filter()
。
const map = (collection, fn) => {
return collection.reduce((acc, item) => {
return acc.concat(fn(item));
}, []);
}
const filter = (collection, fn) => {
return collection.reduce((acc, item) => {
if (fn(item)) {
return acc.concat(item);
}
return acc;
}, []);
}複製程式碼
一開始這一塊兒可能比較難理解。不過,.reduce()
做的基本上就是以一個集合和一個定義了初始值的變數開始。然後,遍歷該集合並將值新增到變數中去。
組合map,filter和reduce
太好了,這些函式我們都有了。而且很重要的一點是,他們都存在於JavaScript的陣列原型上。這意味著我們可以同時使用它們。這可以讓我們輕鬆建立各種可複用的函式,減少編寫某些功能所需要的程式碼量。
我們已經討論過了如何利用.filter()
來過濾出大於等於18歲的人;利用.map()
來新增coffeeLover
屬性;通過.reduce()
來計算所有人年齡的和。現在,我們寫一點程式碼將這三個步驟合併起來。
const people = [
{ name: ‘John Doe’, age: 16 },
{ name: ‘Thomas Calls’, age: 19 },
{ name: ‘Liam Smith’, age: 20 },
{ name: ‘Jessy Pinkman’, age: 18 },
];
const coffeeLovers = [‘John Doe’, ‘Liam Smith’, ‘Jessy Pinkman’];
const ageAbove18 = (person) => person.age >= 18;
const addCoffeeLoverProperty = (person) => {
person.coffeeLover = coffeeLovers.includes(person.name);
return person;
}
const ageReducer = (sum, person) => {
return sum + person.age;
}, 0);
const coffeeLoversAbove18 = people
.filter(ageAbove18)
.map(addCoffeeLoverProperty);
const totalAgeOfCoffeeLoversAbove18 = coffeeLoversAbove18
.reduce(ageReducer);
const totalAge = people
.reduce(ageReducer);複製程式碼
如果你用命令式方法的話,你最後會寫一堆重複程式碼。
通過.map()
,.reduce()
和.filter()
來建立函式的思維將會極大的提高你的程式碼質量。而且可以增加可讀性。你根本不必在意函式內到底發生了什麼,它非常容易理解。