使用reduce方法可高效实现JS数据分组,通过遍历数组并以指定键累积分组结果,支持处理嵌套属性、复合键、键值缺失及类型不一致等复杂场景,结合Map或分批处理可进一步优化性能。

JavaScript中实现分组功能,核心思想其实就是遍历你手头的数据集合,然后根据你预设的一个“规则”或者说“键”,把那些符合相同规则的数据项归拢到一起。说白了,就是把散落在各处的数据,按某种共同点整理成一个个小堆。最常用、也最灵活的实现方式,我个人觉得是利用
Array.prototype.reduce()
要用JS实现分组,最直接且高效的方式是利用数组的
reduce
假设我们有一组用户数据,想根据他们的城市进行分组:
const users = [
{ id: 1, name: 'Alice', city: 'New York' },
{ id: 2, name: 'Bob', city: 'London' },
{ id: 3, name: 'Charlie', city: 'New York' },
{ id: 4, name: 'David', city: 'Paris' },
{ id: 5, name: 'Eve', city: 'London' },
];
// 使用 reduce 实现分组
const groupedByCity = users.reduce((accumulator, currentUser) => {
const city = currentUser.city; // 获取分组的键
if (!accumulator[city]) {
accumulator[city] = []; // 如果这个城市还没有对应的数组,就创建一个
}
accumulator[city].push(currentUser); // 把当前用户添加到对应的城市数组中
return accumulator; // 返回累积器,供下一次迭代使用
}, {}); // 初始值是一个空对象
console.log(groupedByCity);
/*
输出大致会是这样:
{
"New York": [
{ id: 1, name: 'Alice', city: 'New York' },
{ id: 3, name: 'Charlie', city: 'New York' }
],
"London": [
{ id: 2, name: 'Bob', city: 'London' },
{ id: 5, name: 'Eve', city: 'London' }
],
"Paris": [
{ id: 4, name: 'David', city: 'Paris' }
]
}
*/这个过程有点像在整理文件:你拿到一份文件(
currentUser
currentUser.city
accumulator[city]
!accumulator[city]
accumulator
实际开发中,数据结构往往比简单的扁平对象要复杂得多。你可能需要根据嵌套属性分组,或者同时依据多个条件来分组。
对于多层级的数据,例如
user.address.country
const usersWithAddress = [
{ id: 1, name: 'Alice', address: { city: 'New York', country: 'USA' } },
{ id: 2, name: 'Bob', address: { city: 'London', country: 'UK' } },
{ id: 3, name: 'Charlie', address: { city: 'Boston', country: 'USA' } },
{ id: 4, name: 'David', address: { city: 'Paris', country: 'France' } },
];
const groupedByCountry = usersWithAddress.reduce((acc, user) => {
const country = user.address?.country; // 使用可选链操作符,防止 address 不存在
if (country) { // 确保国家存在才进行分组
if (!acc[country]) {
acc[country] = [];
}
acc[country].push(user);
}
return acc;
}, {});
console.log(groupedByCountry);这里我加入了
?.
if (country)
而当需要根据多个条件组合分组时,你可以拼接一个复合键。比如,要同时根据城市和国家分组:
const groupedByCityAndCountry = usersWithAddress.reduce((acc, user) => {
const city = user.address?.city;
const country = user.address?.country;
if (city && country) {
const compositeKey = `${city}-${country}`; // 创建复合键
if (!acc[compositeKey]) {
acc[compositeKey] = [];
}
acc[compositeKey].push(user);
}
return acc;
}, {});
console.log(groupedByCityAndCountry);
/*
输出示例:
{
"New York-USA": [ { ...Alice... } ],
"London-UK": [ { ...Bob... } ],
"Boston-USA": [ { ...Charlie... } ],
"Paris-France": [ { ...David... } ]
}
*/这种复合键的方式非常灵活,你可以根据任意多的属性来生成唯一的键,实现更细粒度的分组。
这是个很现实的问题,数据往往不那么“干净”。如果分组的键值可能缺失(
null
undefined
处理键值缺失: 当某个数据项用于分组的键可能不存在时,如果不做处理,
accumulator[undefined]
accumulator[null]
const products = [
{ id: 1, name: 'Laptop', category: 'Electronics' },
{ id: 2, name: 'Mouse', category: 'Electronics' },
{ id: 3, name: 'Keyboard' }, // 缺少 category
{ id: 4, name: 'Monitor', category: null }, // category 为 null
{ id: 5, name: 'Headphones', category: 'Electronics' }
];
const groupedBySafeCategory = products.reduce((acc, product) => {
// 优先使用实际的 category,如果缺失或为 null/undefined,则使用 'Other'
const category = product.category || 'Other';
if (!acc[category]) {
acc[category] = [];
}
acc[category].push(product);
return acc;
}, {});
console.log(groupedBySafeCategory);
/*
输出示例:
{
"Electronics": [ { ...Laptop... }, { ...Mouse... }, { ...Headphones... } ],
"Other": [ { ...Keyboard... }, { ...Monitor... } ]
}
*/这里我用了
product.category || 'Other'
product.category
undefined
null
''
0
false
'Other'
处理数据类型不一致: 如果你的分组键可能出现类型不一致,比如数字
1
"1"
const items = [
{ id: 1, type: 100 },
{ id: 2, type: '100' }, // 注意这里是字符串
{ id: 3, type: 200 }
];
const groupedByTypeConsistent = items.reduce((acc, item) => {
// 将 type 统一转换为字符串,确保键的唯一性
const typeKey = String(item.type);
if (!acc[typeKey]) {
acc[typeKey] = [];
}
acc[typeKey].push(item);
return acc;
}, {});
console.log(groupedByTypeConsistent);
/*
输出示例:
{
"100": [ { id: 1, type: 100 }, { id: 2, type: '100' } ],
"200": [ { id: 3, type: 200 } ]
}
*/通过
String(item.type)
在处理大量数据时,性能总是值得关注的话题。对于JavaScript中的分组操作,特别是使用
reduce
内存消耗: 分组操作会创建一个新的对象来存储分组后的数据。这个新对象的大小取决于原始数据的数量和分组的粒度。如果分组后的键非常多,或者每个组内的数据项非常多,那么这个结果对象可能会占用大量内存。在浏览器环境中,过大的内存占用可能导致页面卡顿甚至崩溃。
优化策略:
避免不必要的计算: 在
reduce
合理选择数据结构:
{}Map
Map
// 使用 Map 进行分组,键可以是任意类型
const dataWithMixedKeys = [
{ id: 1, groupKey: { a: 1 } },
{ id: 2, groupKey: { a: 1 } }, // 注意:这里是不同的对象实例,Map 会视为不同的键
{ id: 3, groupKey: 100 },
{ id: 4, groupKey: '100' }
];
const groupedByMap = dataWithMixedKeys.reduce((map, item) => {
const key = item.groupKey;
if (!map.has(key)) {
map.set(key, []);
}
map.get(key).push(item);
return map;
}, new Map());
console.log(groupedByMap);
// 注意:由于 {a:1} 是两个不同的对象实例,它们会被视为两个不同的键
// Map(4) {
// { a: 1 } => [ { id: 1, groupKey: { a: 1 } } ],
// { a: 1 } => [ { id: 2, groupKey: { a: 1 } } ],
// 100 => [ { id: 3, groupKey: 100 } ],
// '100' => [ { id: 4, groupKey: '100' } ]
// }对于对象作为键的情况,
Map
分批处理 (Batch Processing): 如果数据量极其庞大,导致一次性处理会阻塞主线程(在浏览器中表现为页面卡顿),可以考虑将数据分批处理。例如,使用
setTimeout
requestAnimationFrame
服务器端处理: 对于百万级别甚至千万级别的数据,前端JS进行分组操作通常是不现实的,也并非其设计初衷。这种情况下,数据处理应该在服务器端完成,由数据库或后端服务提供聚合好的数据。
总的来说,对于前端JS能处理的数据量(几万到几十万条),
Array.prototype.reduce()
reduce
reduce
以上就是JS如何实现分组功能的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号