在实际开发中,我们经常遇到这样的需求:一个键值为对象的JavaScript对象,需要根据某个指定属性(如name)进行去重分组,并生成一个包含计数的新对象,同时输出结果保持数字索引顺序。这涉及几个关键知识点,下面逐步拆解分析。
在日常开发中,我们时常遇到这种场景:一个类似数组的对象,其键为连续的数字字符串,内部存储了多条记录。现在需要根据某个字段(例如 Name)来分组统计频次,即统计每个值出现的次数,最后以紧凑有序的格式重新组织数据。虽然输入的结构看起来像数组,但本质上仍是对象,无法直接使用 Array.prototype 的方法。必须先通过 Object.values() 提取所有值,这是必不可少的起点。
下面给出一个清晰高效且兼容性良好的实现方案:
const input = {
0: { State: true, Name: 'bob' },
1: { State: true, Name: 'candice' },
2: { State: true, Name: 'allen' },
3: { State: true, Name: 'bob' },
4: { State: true, Name: 'bob' },
5: { State: true, Name: 'candice' }
};
// 步骤 1:按 Name 统计频次(空值合并操作符 ?? 确保初始值为 0)
const countMap = {};
for (const { Name } of Object.values(input)) {
countMap[Name] = (countMap[Name] ?? 0) + 1;
}
// 步骤 2:转换为按数字索引排列的目标格式
const result = {};
Object.entries(countMap).forEach(([Name, Count], index) => {
result[index] = { Name, Count };
});
console.log(result);
// 输出:
// {
// 0: { Name: 'bob', Count: 3 },
// 1: { Name: 'candice', Count: 2 },
// 2: { Name: 'allen', Count: 1 }
// }
核心要点解析
- 使用
Object.values(input)提取所有值,这是最安全的方式,能有效避开原型链污染及非数字键的干扰。不要图省事直接遍历整个对象,那样容易引发问题。 ?? 0比|| 0更严谨。原因在于:如果countMap[Name]的值恰好为 0(虽然不常见,但逻辑上完全可能),|| 0会将 0 视为假值而覆盖,而??仅在值为undefined或null时才会生效。细节决定成败,这个小坑值得留意。Object.entries()的输出顺序:它返回[key, value]数组,ES2015+ 规范明确保证了按插入顺序排列。因此示例中 'bob'、'candice'、'allen' 的顺序与它们首次出现的次序一致。如果业务需要按频次降序,则可在之后手动调用.sort()。- 最终结果使用数字字符串作为键(如 '0'、'1' 等),既满足原始需求,也便于后续当作类数组结构处理——例如使用
for...in或Object.keys(result).map(...),都非常方便。
注意事项与优化建议
- 输入对象的键如果不连续,例如混入了 '100'、'abc' 等键名,
Object.values()仍然不受影响,它只提取值,不关心键。但此时需明确业务逻辑是否依赖键的顺序,不可想当然。 - 兼容性处理:若项目需支持 IE 浏览器,可将
??替换为countMap[Name] === undefined ? 0 : countMap[Name]。虽然代码稍显冗余,但稳定性更高。 - 大数据量场景(万级以上),可考虑使用
Map替代普通对象以提升插入与查找性能。但对于绝大多数日常场景,这种优化几乎无感知,切忌过度设计。
按照以上四步实现,代码简洁且可读性强,完全不依赖外部库,既使用了现代语法又保留了向后兼容性——这是解决JavaScript对象按属性值分组频次统计问题的理想方案。
