纯JavaScript手写分组函数代码,同时支持数组分组和对象分组。
/**
* ================= 手写分组函数(数组分组 + 对象分组)=================
* 纯JS实现,无任何依赖
*/
// ------------------- 1. 数组分组 -------------------
/**
* 数组分组 - 按指定条件对数组元素进行分组
* @param {Array} array - 需要分组的数组
* @param {string|Function} keySelector - 分组依据:属性名或自定义函数
* @param {boolean} [unique=false] - 是否去重(同一组内去重)
* @returns {Object} 分组后的对象
*/
function groupArray(array, keySelector, unique = false) {
return array.reduce((result, item, index) => {
// 计算分组键
const key = typeof keySelector === 'function'
? keySelector(item, index, array)
: item[keySelector];
// 处理null/undefined键
const groupKey = key === null ? 'null'
: key === undefined ? 'undefined'
: key;
// 如果该组不存在,初始化
if (!Object.hasOwnProperty.call(result, groupKey)) {
result[groupKey] = [];
}
// 如果需要去重,检查是否已存在
if (unique) {
const exists = result[groupKey].some(existingItem =>
JSON.stringify(existingItem) === JSON.stringify(item)
);
if (!exists) {
result[groupKey].push(item);
}
} else {
result[groupKey].push(item);
}
return result;
}, {});
}
/**
* 数组分组 - 返回Map版本(支持任意类型键)
* @param {Array} array - 需要分组的数组
* @param {string|Function} keySelector - 分组依据
* @returns {Map} Map对象,键可以是任意类型
*/
function groupArrayToMap(array, keySelector) {
return array.reduce((map, item, index) => {
const key = typeof keySelector === 'function'
? keySelector(item, index, array)
: item[keySelector];
if (!map.has(key)) {
map.set(key, []);
}
map.get(key).push(item);
return map;
}, new Map());
}
/**
* 数组分组 - 多级分组
* @param {Array} array - 需要分组的数组
* @param {Array} keySelectors - 分组依据数组,如 ['dept', 'level']
* @returns {Object} 嵌套的分组对象
*/
function groupArrayMulti(array, keySelectors) {
if (!keySelectors || keySelectors.length === 0) {
return array;
}
const currentKey = keySelectors[0];
const remainingKeys = keySelectors.slice(1);
// 先按第一个键分组
const grouped = groupArray(array, currentKey);
// 如果还有剩余键,递归分组
if (remainingKeys.length > 0) {
Object.keys(grouped).forEach(key => {
grouped[key] = groupArrayMulti(grouped[key], remainingKeys);
});
}
return grouped;
}
/**
* 数组分组 - 按范围分组(数值范围)
* @param {Array} array - 需要分组的数组
* @param {string} field - 数值字段名
* @param {Array} ranges - 范围数组,如 [{min:0,max:20}, {min:20,max:40}]
* @returns {Object} 分组结果
*/
function groupArrayByRange(array, field, ranges) {
return array.reduce((result, item) => {
const value = item[field];
let rangeKey = '其他';
for (let i = 0; i < ranges.length; i++) {
const range = ranges[i];
if (value >= range.min && value < range.max) {
rangeKey = `${range.min}-${range.max}`;
break;
}
}
if (!result[rangeKey]) {
result[rangeKey] = [];
}
result[rangeKey].push(item);
return result;
}, {});
}
// ------------------- 2. 对象分组 -------------------
/**
* 对象分组 - 按属性值对对象进行分组
* @param {Object} obj - 需要分组的对象
* @param {Function} keySelector - 从每个值中提取分组键的函数
* @returns {Object} 分组后的对象
*/
function groupObject(obj, keySelector) {
return Object.entries(obj).reduce((result, [key, value]) => {
const groupKey = keySelector(value, key, obj);
// 处理null/undefined
const finalKey = groupKey === null ? 'null'
: groupKey === undefined ? 'undefined'
: groupKey;
if (!result[finalKey]) {
result[finalKey] = {};
}
result[finalKey][key] = value;
return result;
}, {});
}
/**
* 对象分组 - 按值类型分组
* @param {Object} obj - 需要分组的对象
* @returns {Object} 按类型分组的结果
*/
function groupObjectByType(obj) {
return Object.entries(obj).reduce((result, [key, value]) => {
const type = value === null ? 'null'
: Array.isArray(value) ? 'array'
: typeof value;
if (!result[type]) {
result[type] = {};
}
result[type][key] = value;
return result;
}, {});
}
/**
* 对象分组 - 按值范围分组(适用于数值)
* @param {Object} obj - 需要分组的对象
* @param {Array} ranges - 范围数组
* @returns {Object} 分组结果
*/
function groupObjectByValueRange(obj, ranges) {
return Object.entries(obj).reduce((result, [key, value]) => {
if (typeof value !== 'number') {
if (!result.other) result.other = {};
result.other[key] = value;
return result;
}
let rangeKey = '其他';
for (let i = 0; i < ranges.length; i++) {
const range = ranges[i];
if (value >= range.min && value < range.max) {
rangeKey = `${range.min}-${range.max}`;
break;
}
}
if (!result[rangeKey]) {
result[rangeKey] = {};
}
result[rangeKey][key] = value;
return result;
}, {});
}
// ------------------- 3. 通用工具函数 -------------------
/**
* 通用分组接口 - 自动识别数组或对象
* @param {Array|Object} data - 需要分组的数据
* @param {string|Function} keySelector - 分组依据
* @param {Object} options - 配置选项
* @returns {Object|Map} 分组结果
*/
function groupBy(data, keySelector, options = {}) {
const { returnMap = false, unique = false, multiLevel = false } = options;
// 数组分组
if (Array.isArray(data)) {
if (returnMap) {
return groupArrayToMap(data, keySelector);
}
if (multiLevel && Array.isArray(keySelector)) {
return groupArrayMulti(data, keySelector);
}
return groupArray(data, keySelector, unique);
}
// 对象分组
else if (data && typeof data === 'object') {
if (typeof keySelector !== 'function') {
throw new Error('对象分组时keySelector必须是函数');
}
return groupObject(data, keySelector);
}
throw new Error('数据必须是数组或对象');
}
// ------------------- 4. 完整示例和测试 -------------------
// 测试数据
const people = [
{ id: 1, name: '赵', dept: '技术', age: 25, salary: 8000 },
{ id: 2, name: '钱', dept: '市场', age: 31, salary: 12000 },
{ id: 3, name: '孙', dept: '技术', age: 29, salary: 9500 },
{ id: 4, name: '李', dept: '市场', age: 27, salary: 11000 },
{ id: 5, name: '周', dept: '产品', age: 35, salary: 15000 },
{ id: 6, name: '吴', dept: '技术', age: 22, salary: 7000 },
{ id: 7, name: '赵', dept: '技术', age: 25, salary: 8000 } // 重复数据
];
const testObj = {
user1: { name: '赵', age: 25, dept: '技术' },
user2: { name: '钱', age: 31, dept: '市场' },
user3: { name: '孙', age: 29, dept: '技术' },
user4: { name: '李', age: 27, dept: '市场' },
user5: { name: '周', age: 35, dept: '产品' },
score1: 95,
score2: 87,
score3: 76,
title: '测试',
active: true,
tags: ['js', '分组'],
empty: null
};
// 测试1:数组基础分组
console.log('===== 1. 数组基础分组 =====');
console.log('按部门分组:', groupArray(people, 'dept'));
// 测试2:数组分组(去重)
console.log('\n===== 2. 数组分组(去重) =====');
console.log('按部门分组(去重):', groupArray(people, 'dept', true));
// 测试3:数组自定义函数分组
console.log('\n===== 3. 自定义函数分组 =====');
const byAgeRange = groupArray(people, p => {
if (p.age < 25) return '20-24岁';
if (p.age < 30) return '25-29岁';
if (p.age < 35) return '30-34岁';
return '35岁及以上';
});
console.log('按年龄范围分组:', byAgeRange);
// 测试4:Map版本
console.log('\n===== 4. Map版本 =====');
const mapResult = groupArrayToMap(people, 'dept');
console.log('Map结果:', mapResult);
console.log('技术部门人数:', mapResult.get('技术').length);
// 测试5:多级分组
console.log('\n===== 5. 多级分组 =====');
const multiGroup = groupArrayMulti(people, ['dept', 'age']);
console.log('按部门-年龄多级分组:', JSON.stringify(multiGroup, null, 2));
// 测试6:范围分组
console.log('\n===== 6. 范围分组 =====');
const salaryRanges = [
{ min: 0, max: 8000 },
{ min: 8000, max: 10000 },
{ min: 10000, max: 15000 },
{ min: 15000, max: Infinity }
];
const bySalary = groupArrayByRange(people, 'salary', salaryRanges);
console.log('按薪资范围分组:', bySalary);
// 测试7:对象分组
console.log('\n===== 7. 对象分组 =====');
const groupedObj = groupObject(testObj, (value) => {
if (value && typeof value === 'object' && !Array.isArray(value)) return 'objects';
if (Array.isArray(value)) return 'arrays';
return typeof value;
});
console.log('对象按值类型分组:', groupedObj);
// 测试8:对象按类型分组
console.log('\n===== 8. 对象按类型分组 =====');
const byType = groupObjectByType(testObj);
console.log('按JavaScript类型分组:', byType);
// 测试9:对象按数值范围分组
console.log('\n===== 9. 对象按数值范围分组 =====');
const scoreRanges = [
{ min: 90, max: 100 },
{ min: 80, max: 90 },
{ min: 70, max: 80 },
{ min: 0, max: 70 }
];
const byScoreRange = groupObjectByValueRange({
math: 95,
english: 87,
chinese: 76,
physics: 68,
name: '测试'
}, scoreRanges);
console.log('按分数范围分组:', byScoreRange);
// 测试10:通用接口
console.log('\n===== 10. 通用分组接口 =====');
console.log('通用接口-数组:', groupBy(people, 'dept'));
console.log('通用接口-对象:', groupBy(testObj, v => typeof v));
// 测试11:性能测试
console.log('\n===== 11. 性能测试 =====');
function generateTestData(count) {
const depts = ['技术', '市场', '产品', '设计', '运营'];
const data = [];
for (let i = 0; i < count; i++) {
data.push({
id: i,
dept: depts[Math.floor(Math.random() * depts.length)],
age: 20 + Math.floor(Math.random() * 30),
salary: 5000 + Math.floor(Math.random() * 15000)
});
}
return data;
}
const bigData = generateTestData(100000);
console.time('数组分组性能');
const bigResult = groupArray(bigData, 'dept');
console.timeEnd('数组分组性能');
console.log(`数据量:${bigData.length},分组数:${Object.keys(bigResult).length}`);
// ------------------- 5. 极简版本 -------------------
/**
* 最简版本 - 一行代码实现数组分组
*/
const simpleGroupArray = (arr, fn) =>
arr.reduce((acc, item) => ((acc[typeof fn === 'function' ? fn(item) : item[fn]] = acc[typeof fn === 'function' ? fn(item) : item[fn]] || []).push(item), acc), {});
/**
* 最简版本 - 对象分组
*/
const simpleGroupObject = (obj, fn) =>
Object.entries(obj).reduce((acc, [k, v]) => ((acc[fn(v, k)] = acc[fn(v, k)] || {})[k] = v, acc), {});
// 测试极简版本
console.log('\n===== 12. 极简版本测试 =====');
console.log('极简数组分组:', simpleGroupArray(people, 'dept'));
console.log('极简对象分组:', simpleGroupObject(testObj, v => typeof v));
这个纯JS代码包含了:
📦 数组分组功能
groupArray- 基础数组分组(支持去重)groupArrayToMap- 返回Map对象(支持任意类型键)groupArrayMulti- 多级分组(如先按部门,再按年龄)groupArrayByRange- 按数值范围分组
📋 对象分组功能
groupObject- 基础对象分组groupObjectByType- 按JavaScript类型分组groupObjectByValueRange- 按数值范围分组
🎯 通用工具
groupBy- 自动识别数组/对象的统一接口- 多个极简版本实现
✨ 特点
- 纯JS实现,零依赖
- 处理null/undefined边界情况
- 支持去重选项
- 包含完整示例和性能测试
- 多种风格实现(常规版、Map版、极简版)