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