如果你剛接觸JavaScript可能你還沒有聽說過.map()
,.reduce()
,.filter()
。或者聽說過,看過別人用過但是自己在實際項目中沒有用過。在國內很多開發項目都是需要考慮IE8的兼容,爲了兼容很多JavaScript好用的方法和技巧都被埋沒了。但是我發現近幾年開始,很多開發項目已經完全拋棄了IE這個魔鬼了。如果你不需要兼容“石器時代”的IE瀏覽器了,那就要開始熟悉一下這幾個方法來處理數組。
注意這遍文章說的的3個方法其實在很多其他語言都可以使用到,因爲這幾個方法和使用概念在很多其他語言都是存在的。
.map()
讓我用一個簡單的例子告訴你如何使用這個方法。假如你現在有多對象的數組數據 - 每一個對象代表着一個員工的信息。現在你想要的最終結果就是取出所有員工的唯一ID值。
// 員工數據
var employees = [
{ id: 20, name: 'Captain Piett' },
{ id: 24, name: 'General Veers' },
{ id: 56, name: 'Admiral Ozzel' },
{ id: 88, name: 'Commander Jerjerrod' }
];
// 你想要的結果
[20, 24, 56, 88]
其實要實現這個結果有很多數組處理方式。傳統的處理方法就是先定義一個空數組,然後使用.forEach()
,.for(...of)
,或者是最簡單的.for()
來組裝ID到你定義的數組裏面。
我們來對比一下傳統的處理方式和.map()
的區別。
使用.forEach()
:
var employeeIds = [];
employees.forEach(function (employee) {
employeeIds.push(officer.id);
});
注意使用傳統的方式,我們必須有一個預定義的空數組變量纔行。但是如果是.map()
就會更簡單了。
var employeeIds = employees.map(function (employee) {
return employee.id
});
甚至我們可以用更簡潔的方式,使用箭頭方法(但是需要ES6支持,Babel,或者TypeScript)。
const employeeIds = employees.map(employee => employee.id);
所以.map()
到底是怎麼運作的呢?這個方法有兩個參數,第一是回調方法,第二是可選內容(會在回調方法中做爲this
)。數組裏的每個數值/對象會被循環進入到回調方法
裏面,然後返回新的數值/對象
到結果數組裏面。
注意結果數組的長度永遠都會和被循環的數組的長度一致。
.reduce()
與.map()
相識,.reduce()
也是循環一個回調方法,數組裏面的每一個元素對回進入回調方法。區別是回調方法返回的值會被傳遞到下一個回調方法,如此類推(等同於一個累加器)。
.reduce()
裏的累加值可以是任何屬性的值,包括integer
,string
,object
等等。這個累加值會被實力化或者傳遞到下一個回調方法。
來上代碼,做個簡單的例子!假如你有一個飛機師的數組,數組裏面有每個飛機師的工齡。
var pilots = [
{
id: 10,
name: "Poe Dameron",
years: 14,
},
{
id: 2,
name: "Temmin 'Snap' Wexley",
years: 30,
},
{
id: 41,
name: "Tallissan Lintra",
years: 16,
},
{
id: 99,
name: "Ello Asty",
years: 22,
}
];
現在我們需要知道所有飛機師累計的總工齡。使用.reduce()
就是比喫飯還簡單的事情。
var totalYears = pilots.reduce(function (accumulator, pilot) {
return accumulator + pilot.years;
}, 0);
注意我這裏第二個參數我傳了0。第二個參數是一個累加值的初始值。當然如果場景需要這個初始值也可以傳入一個變量或者你需要的值。循環了數組裏的每一個元素後,reduce方法會返回最終累加後的值(在我們這個例子中就是82
)。
例子裏面的
acc
和accumulator
就是累加值變量
如果是使用ES6箭頭寫法,我們可以寫的更加優雅簡潔。一行就可以搞掂的事情!
const totalYears = pilots.reduce((acc, pilot) => acc + pilot.years, 0);
現在如果我們需要找到哪一位是最有經驗的飛機師。這種情況我們一樣可以使用.reduce()
。
var mostExpPilot = pilots.reduce(function (oldest, pilot) {
return (oldest.years || 0) > pilot.years ? oldest : pilot;
}, {});
這裏我把accumulator
變量改爲oldest
代表飛機師裏面的老司機。這時候reduce裏面的回調方法對比每一個飛機師,每一次飛機師的值進入這個回調方法,工齡更高的就會覆蓋oldest
變量。最終循環後得到的oldest
就是工齡最高的飛機師。
通過這幾個例子,你可以看到使用.reduce()
可以簡單又優雅的在一個數組裏面獲取到單個最終值或者對象。
.filter()
如果你現在的場景是需要在一個數組裏面過濾一部分的數據,這個時候.filter()
就是你的最好的朋友了。
我們用回飛機師的數據,並且加入了所屬航空公司的值:
var pilots = [
{
id: 2,
name: "Wedge Antilles",
faction: "Rebels",
},
{
id: 8,
name: "Ciena Ree",
faction: "Empire",
},
{
id: 40,
name: "Iden Versio",
faction: "Empire",
},
{
id: 66,
name: "Thane Kyrell",
faction: "Rebels",
}
];
加入現在我們想分別篩選出Rebels
和Empire
兩個航空公司的飛機師,使用.filter()
就是輕而易舉的事情!
var rebels = pilots.filter(function (pilot) {
return pilot.faction === "Rebels";
});
var empire = pilots.filter(function (pilot) {
return pilot.faction === "Empire";
});
就這麼簡單,如果使用箭頭方法(ES6)就更加優雅了:
const rebels = pilots.filter(pilot => pilot.faction === "Rebels");
const empire = pilots.filter(pilot => pilot.faction === "Empire");
其實原理很簡單,只要你的回調方法返回的是true
,這個值或者對象就會在新的數組裏面了。如果返回的是false
就會被過濾掉了。
結合使用 .map(),.reduce(),.filter()
既然我們剛剛學到的三個函數都是可以用於數組的,並且.map()
和.filter()
都是返回數組的。那我們就可以串聯起來使用。不說多了上代碼試試!
我們用一個有趣一點的數據試驗一下,假如現在我們有一個星球大戰
裏面的人物
的數組。每個字段的定義如下:
Id
: 人物唯一IDname
: 人物名字pilotingScore
: 飛行能力指數shootingScore
: 射擊能力指數isForceUser
: 是否擁有隔空操控能力
我們的目標:獲取擁有隔空操控能力的飛行員的總飛行能力指數
。我們先分開一步一步實現這個目標!
- 首先我們需要先獲取到擁有隔空操控能力的飛行員。
var jediPersonnel = personnel.filter(function (person) {
return person.isForceUser;
});
// 結果集: [{...}, {...}, {...}] (Luke, Ezra and Caleb)
- 這段代碼我們獲得了3個飛行員對象,分別都是擁有隔空操控能力的飛行員。使用這個對象我們來獲取每個飛行員的飛行能力指數值。
var jediScores = jediPersonnel.map(function (jedi) {
return jedi.pilotingScore + jedi.shootingScore;
});
// 結果: [154, 110, 156]
- 獲取到每個飛行員的飛行能力指數值後,我們就可以用累加器(
.reduce()
)獲取總飛行能力指數了。
var totalJediScore = jediScores.reduce(function (acc, score) {
return acc + score;
}, 0);
// 結果: 420
這裏分開實現方式可以達到我們的目標,但是其實我們可以串聯起來,可以寫的更加簡潔又優雅!我們來玩玩更好玩的吧!
var totalJediScore = personnel
.filter(function (person) {
return person.isForceUser;
})
.map(function (jedi) {
return jedi.pilotingScore + jedi.shootingScore;
})
.reduce(function (acc, score) {
return acc + score;
}, 0);
這樣寫是不是很優雅!都被這段代碼給美到了!❤️
如果我們使用箭頭寫法ES6,就更加優雅了!
const totalJediScore = personnel
.filter(person => person.isForceUser)
.map(jedi => jedi.pilotingScore + jedi.shootingScore)
.reduce((acc, score) => acc + score, 0);
哇!代碼原來可以寫的那麼優雅的麼?!想不到吧?
其實我們只需要使用
.reduce()
就可以得到我們的目標結果了,以上例子做爲教學例子,所以使用了3個我們學到的函數。我們來看看只用
.reduce()
怎麼實現的,來我們一起來刷新一下三觀吧!
const totalJediScore = personnel.reduce((acc, person) => person.isForceUser ? acc + person.pilotingScore + person.shootingScore : acc, 0);
不敢想象吧?一行就搞定一個功能不是夢!
爲什麼拋棄 .forEach()?
其實我一開始寫前端的時候也是一頓擼,來個數組都是擼個for循環,解決一切數組處理問題。但是近幾年我開始步入前後端開發,API接口對接。發現數據處理越來越多,如果還是像以前那樣什麼都用for循環來處理數據,那其實數據處理的代碼就會越來越臃腫越來越複雜凌亂。所以我開始拋棄了.forEach()
。開始做一個優雅的程序員!
爲什麼使用.map()
,.filter()
,.reduce()
寫代碼更優雅,更美觀呢?我們用一個實戰例子來對比一下吧。
假設現在我們對接一個接口,返回的數組裏面有兩個字段name:人的名稱
和title:對應的職位
。
var data = [
{
name: "Jan Dodonna",
title: "General",
},
{
name: "Gial Ackbar",
title: "Admiral",
},
]
產品經理給到你的需求是隻需要展示這些人的職位稱呼。
當然這個時候有一些前端就會說“我只是個小小的前端,後端給我處理吧”。但是,這個接口其實是一個通用的接口,就是獲取這些員工的資料的,是在多個地方使用的。如果每一個頁面因爲需要展示的不一樣而要寫多一個接口給你,你覺得這樣好嗎?做爲一個優秀的前端工程師🦁️,這種小case你自己就可以很優雅的處理好了。而且,在一個優秀的團隊,後端確實是要考慮接口通用性的,這種爲了你的方便而給他們帶來更臃腫的接口是不可接受的。所以前端這個時候就是要重組數據了。
假設現在產品給你的需求是員工列表中,要支持只展示員工職稱和員工信息的兩種顯示項。這個時候我們就要編寫一個數據組裝方法來跟進展示要求來改變數據格式。
因爲這個“騷“需求,我們使用.forEach()
來重組數據就相對比較麻煩了,而且代碼也會變得臃腫。
我們忽略了組裝數據的方法,直接就當作我們已經寫好了一個組裝數據的方法爲formatElement
。如果我們用forEach
,那首先就需要定義一個空數組來接收結果。
var results = [];
data.forEach(function (element) {
var formatted = formatElement(element);
results.push(formatted);
});
所以我們需要兩個方法才能實現這個數據結果,但是爲什麼要寫的那麼臃腫呢?因爲forEach
並沒有返回值,單單就給你跑個循環,還需要自己push
值到預定義的變量裏面。其實一個方法就可以完成了,而且重點是一行代碼就完事了。
來使用我們新學的技巧,用.map()
來實現就非常簡單優雅了。
var results = data.map(formatElement);
總結
你學會了嗎?學會了就去嘗試用.map()
,.reduce()
,.filter()
來替換你傳統的for
循環吧!我保證你的代碼會越來越簡潔,可讀性更高。
如果你喜歡我的這遍文章,記得繼續關注我的博客,下一遍文章我們開學習怎麼在JavaScript中使用.some()
和.find()
。
堅持做一個優雅的程序員,堅持每天敲代碼!