在日常開(kāi)發(fā)中,很多時(shí)候需要對(duì)數(shù)組進(jìn)行分組,每次都要手寫(xiě)一個(gè)分組函數(shù),或者使用lodash的groupBy
函數(shù)。好消息是,JAVAScript 現(xiàn)在正在引入全新的分組方法:Object.groupBy
和Map.groupBy
,以后再也不需要手寫(xiě)分組函數(shù)了,目前最新版本的 Chrome(117)已經(jīng)支持了這兩個(gè)方法!
以前的數(shù)組分組
假設(shè)有一個(gè)由表示人員的對(duì)象組成的數(shù)組,需要按照年齡進(jìn)行分組??梢允褂?code data-line="3">forEach循環(huán)來(lái)實(shí)現(xiàn),代碼如下:
const people = [
{ name: "Alice", age: 28 },
{ name: "Bob", age: 30 },
{ name: "Eve", age: 28 },
];
const peopleByAge = {};
people.forEach((person) => {
const age = person.age;
if (!peopleByAge[age]) {
peopleByAge[age] = [];
}
peopleByAge[age].push(person);
});
console.log(peopleByAge);
輸出結(jié)果如下:
{
"28": [{"name":"Alice","age":28}, {"name":"Eve","age":28}],
"30": [{"name":"Bob","age":30}]
}
也可以使用reduce
方法:
const peopleByAge = people.reduce((acc, person) => {
const age = person.age;
if (!acc[age]) {
acc[age] = [];
}
acc[age].push(person);
return acc;
}, {});
無(wú)論哪種方式,代碼都略顯繁瑣。每次都要檢查對(duì)象,看分組鍵是否存在,如果不存在,則創(chuàng)建一個(gè)空數(shù)組,并將項(xiàng)目添加到該數(shù)組中。
使用 Object.groupBy 分組
可以通過(guò)以下方式來(lái)使用新的Object.groupBy
方法:
const peopleByAge = Object.groupBy(people, (person) => person.age);
可以看到,代碼非常簡(jiǎn)潔!
不過(guò)需要注意,使用Object.groupBy
方法返回一個(gè)沒(méi)有原型(即沒(méi)有繼承任何屬性和方法)的對(duì)象。這意味著該對(duì)象不會(huì)繼承Object.prototype
上的任何屬性或方法,例如hasOwnProperty
或toString
等。雖然這樣做可以避免意外覆蓋Object.prototype
上的屬性,但也意味著不能使用一些與對(duì)象相關(guān)的方法。
const peopleByAge = Object.groupBy(people, (person) => person.age);
console.log(peopleByAge.hasOwnProperty("28"));
// TypeError: peopleByAge.hasOwnProperty is not a function
在調(diào)用Object.groupBy
時(shí),傳遞給它的回調(diào)函數(shù)應(yīng)該返回一個(gè)字符串或 Symbol
類型的值。如果回調(diào)函數(shù)返回其他類型的值,它將被強(qiáng)制轉(zhuǎn)換為字符串。
在這個(gè)例子中,回調(diào)函數(shù)返回的是一個(gè)數(shù)字類型的age
屬性值,但由于Object.groupBy
方法要求鍵必須是字符串或 Symbol
類型,所以該數(shù)字會(huì)被強(qiáng)制轉(zhuǎn)換為字符串類型。
console.log(peopleByAge[28]);
// => [{"name":"Alice","age":28}, {"name":"Eve","age":28}]
console.log(peopleByAge["28"]);
// => [{"name":"Alice","age":28}, {"name":"Eve","age":28}]
使用 Map.groupBy 分組
Map.groupBy
和Object.groupBy
幾乎做的是相同的事情,只是返回的結(jié)果類型不同。Map.groupBy
返回一個(gè)Map對(duì)象,而不是像Object.groupBy
返回一個(gè)普通的對(duì)象。、
const ceo = { name: "Jamie", age: 40, reportsTo: null };
const manager = { name: "Alice", age: 28, reportsTo: ceo };
const people = [
ceo
manager,
{ name: "Bob", age: 30, reportsTo: manager },
{ name: "Eve", age: 28, reportsTo: ceo },
];
const peopleByManager = Map.groupBy(people, (person) => person.reportsTo);
這里根據(jù)人的匯報(bào)上級(jí)將他們進(jìn)行了分組。如果想通過(guò)對(duì)象來(lái)從這個(gè)Map中獲取數(shù)據(jù),那么要求這些對(duì)象具有相同的身份或引用。這是因?yàn)镸ap在比較鍵時(shí)使用的是嚴(yán)格相等(===),只有兩個(gè)對(duì)象具有相同的引用,才能被認(rèn)為是相同的鍵。
peopleByManager.get(ceo);
// => [{ name: "Alice", age: 28, reportsTo: ceo }, { name: "Eve", age: 28, reportsTo: ceo }]
peopleByManager.get({ name: "Jamie", age: 40, reportsTo: null });
// => undefined
在上面的例子中,如果嘗試使用與ceo
對(duì)象類似的對(duì)象作為鍵去訪問(wèn)Map中的項(xiàng),由于這個(gè)對(duì)象與之前存儲(chǔ)在Map中的ceo
對(duì)象不是同一個(gè)對(duì)象,所以無(wú)法檢索到對(duì)應(yīng)的值。
瀏覽器支持
這兩個(gè)groupBy
方法是 proposal-array-grouping 提案提出的,該提案目前處于第3階段,預(yù)計(jì)會(huì)在 2024 年成為正式標(biāo)準(zhǔn)。
9 月 12 日,Chrome 117發(fā)布,該版本支持了這兩個(gè)方法。Firefox Nightly 在一個(gè)標(biāo)志后已經(jīng)實(shí)現(xiàn)了這兩個(gè)方法。Safari已經(jīng)以不同的名稱實(shí)現(xiàn)了這些方法。由于這些方法在 Chrome 中可用,這意味著它們已經(jīng)在V8中被實(shí)現(xiàn),所以下一次V8更新時(shí)它們將在Node中可用。
為什么要使用靜態(tài)方法?
你可能會(huì)想,為什么這個(gè)功能被實(shí)現(xiàn)為Object.groupBy
而不是Array.prototype.groupBy
。根據(jù)提案,有一個(gè)庫(kù)曾經(jīng)用不兼容的groupBy
方法對(duì)Array.prototype
進(jìn)行了修改。在考慮為Web新增API時(shí),向后兼容性非常重要。幾年前,在嘗試實(shí)現(xiàn)Array.prototype.flatten
時(shí)就出現(xiàn)了一個(gè)稱為SmooshGate
的事件。
使用靜態(tài)方法實(shí)際上對(duì)未來(lái)的可擴(kuò)展性更好。當(dāng)Records
和Tuples
提案實(shí)現(xiàn)時(shí),可以添加一個(gè)Record.groupBy
方法,用于將數(shù)組分組為不可變記錄。
簡(jiǎn)而言之,使用靜態(tài)方法可以更好地保持向后兼容性,并提供更好的擴(kuò)展性,以便在未來(lái)添加更多功能和數(shù)據(jù)結(jié)構(gòu)。
JavaScript 正在填補(bǔ)這些空白,并使我們的開(kāi)發(fā)更簡(jiǎn)單。目前,lodash.groupBy
每周的 npm 下載量在 150 萬(wàn)至 200 萬(wàn)次之間,當(dāng)所有瀏覽器都支持該方法之后,就不再需要引入lodash.groupBy
庫(kù)了!