在日常開發中,很多時候需要對數組進行分組,每次都要手寫一個分組函數,或者使用lodash的groupBy函數。好消息是,JavaScript 現在正在引入全新的分組方法:Object.groupBy和Map.groupBy,以後再也不需要手寫分組函數了,目前最新版本的 Chrome(117)已經支持 ...
在日常開發中,很多時候需要對數組進行分組,每次都要手寫一個分組函數,或者使用lodash的groupBy
函數。好消息是,JavaScript 現在正在引入全新的分組方法:Object.groupBy
和Map.groupBy
,以後再也不需要手寫分組函數了,目前最新版本的 Chrome(117)已經支持了這兩個方法!
以前的數組分組
假設有一個由表示人員的對象組成的數組,需要按照年齡進行分組。可以使用forEach
迴圈來實現,代碼如下:
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);
輸出結果如下:
{
"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;
}, {});
無論哪種方式,代碼都略顯繁瑣。每次都要檢查對象,看分組鍵是否存在,如果不存在,則創建一個空數組,並將項目添加到該數組中。
使用 Object.groupBy 分組
可以通過以下方式來使用新的Object.groupBy
方法:
const peopleByAge = Object.groupBy(people, (person) => person.age);
可以看到,代碼非常簡潔!
不過需要註意,使用Object.groupBy
方法返回一個沒有原型(即沒有繼承任何屬性和方法)的對象。這意味著該對象不會繼承Object.prototype
上的任何屬性或方法,例如hasOwnProperty
或toString
等。雖然這樣做可以避免意外覆蓋Object.prototype
上的屬性,但也意味著不能使用一些與對象相關的方法。
const peopleByAge = Object.groupBy(people, (person) => person.age);
console.log(peopleByAge.hasOwnProperty("28"));
// TypeError: peopleByAge.hasOwnProperty is not a function
在調用Object.groupBy
時,傳遞給它的回調函數應該返回一個字元串或 Symbol
類型的值。如果回調函數返回其他類型的值,它將被強制轉換為字元串。
在這個例子中,回調函數返回的是一個數字類型的age
屬性值,但由於Object.groupBy
方法要求鍵必須是字元串或 Symbol
類型,所以該數字會被強制轉換為字元串類型。
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
幾乎做的是相同的事情,只是返回的結果類型不同。Map.groupBy
返回一個Map對象,而不是像Object.groupBy
返回一個普通的對象。、
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);
這裡根據人的彙報上級將他們進行了分組。如果想通過對象來從這個Map中獲取數據,那麼要求這些對象具有相同的身份或引用。這是因為Map在比較鍵時使用的是嚴格相等(===),只有兩個對象具有相同的引用,才能被認為是相同的鍵。
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
對象類似的對象作為鍵去訪問Map中的項,由於這個對象與之前存儲在Map中的ceo
對象不是同一個對象,所以無法檢索到對應的值。
瀏覽器支持
這兩個groupBy
方法是 proposal-array-grouping 提案提出的,該提案目前處於第3階段,預計會在 2024 年成為正式標準。
9 月 12 日,Chrome 117發佈,該版本支持了這兩個方法。Firefox Nightly 在一個標誌後已經實現了這兩個方法。Safari已經以不同的名稱實現了這些方法。由於這些方法在 Chrome 中可用,這意味著它們已經在V8中被實現,所以下一次V8更新時它們將在Node中可用。
為什麼要使用靜態方法?
你可能會想,為什麼這個功能被實現為Object.groupBy
而不是Array.prototype.groupBy
。根據提案,有一個庫曾經用不相容的groupBy
方法對Array.prototype
進行了修改。在考慮為Web新增API時,向後相容性非常重要。幾年前,在嘗試實現Array.prototype.flatten
時就出現了一個稱為SmooshGate
的事件。
使用靜態方法實際上對未來的可擴展性更好。當Records
和Tuples
提案實現時,可以添加一個Record.groupBy
方法,用於將數組分組為不可變記錄。
簡而言之,使用靜態方法可以更好地保持向後相容性,並提供更好的擴展性,以便在未來添加更多功能和數據結構。
JavaScript 正在填補這些空白,並使我們的開發更簡單。目前,lodash.groupBy
每周的 npm 下載量在 150 萬至 200 萬次之間,當所有瀏覽器都支持該方法之後,就不再需要引入lodash.groupBy
庫了!