JS实现嵌套数组数据扁平化为目标键值对格式
在前端数据处理场景中,我们经常会遇到嵌套数组结构的数据,需要将其扁平化后转换为特定格式的键值对。比如后台返回的树形菜单数据、多级分类数据等,往往需要将嵌套结构拆解为扁平的键值对,方便后续进行数据查询、状态管理或者渲染操作。本文将结合具体示例,讲解几种常用的实现方案。
示例场景说明
假设我们有以下嵌套数组数据,每个数组元素包含id、name字段,以及可能存在的children子数组:
const nestedData = [
{
id: 1,
name: '水果',
children: [
{ id: 11, name: '苹果' },
{ id: 12, name: '香蕉', children: [{ id: 121, name: '芭蕉' }] }
]
},
{
id: 2,
name: '蔬菜',
children: [
{ id: 21, name: '白菜' },
{ id: 22, name: '萝卜' }
}
];我们的目标是将上述嵌套数组转换为如下键值对格式,其中键为元素的id,值为元素的name:
const targetResult = {
1: '水果',
11: '苹果',
12: '香蕉',
121: '芭蕉',
2: '蔬菜',
21: '白菜',
22: '萝卜'
};方案一:递归遍历实现
递归是处理嵌套结构最直观的思路,我们可以编写一个递归函数,遍历数组的每个元素,将当前元素的id和name存入结果对象,若存在children子数组则继续递归处理子数组。
function flattenToKeyValue(arr, result = {}) {
arr.forEach(item => {
// 存储当前元素的键值对
result[item.id] = item.name;
// 若存在children子数组,递归处理
if (item.children && Array.isArray(item.children)) {
flattenToKeyValue(item.children, result);
}
});
return result;
}
// 调用函数
const result1 = flattenToKeyValue(nestedData);
console.log(result1);
// 输出结果与targetResult一致该方案的逻辑清晰,适合嵌套层级不固定的场景,时间复杂度为O(n),n为所有元素的总个数。需要注意的是,如果数据源中存在重复的id,后遍历到的元素会覆盖之前存储的同键名值。
方案二:栈迭代实现
递归虽然直观,但如果嵌套层级过深可能导致栈溢出,此时可以使用栈的迭代方式实现,避免递归调用带来的栈溢出风险。
function flattenToKeyValueByStack(arr) {
const result = {};
// 初始化栈,将初始数组的所有元素入栈
const stack = [...arr];
while (stack.length > 0) {
const current = stack.pop();
// 存储当前元素的键值对
result[current.id] = current.name;
// 若存在children子数组,将子数组元素入栈
if (current.children && Array.isArray(current.children)) {
stack.push(...current.children);
}
}
return result;
}
// 调用函数
const result2 = flattenToKeyValueByStack(nestedData);
console.log(result2);
// 输出结果与targetResult一致该方案通过栈模拟递归的遍历过程,同样可以处理任意层级的嵌套数组,且没有递归深度限制,适合处理层级特别深的数据场景。
方案三:支持自定义键名的通用版本
如果实际场景中需要自定义的键和值对应的字段名,比如键不一定用id,值不一定用name,我们可以给函数增加配置参数,让方法更通用。
function flattenToCustomKeyValue(arr, keyField = 'id', valueField = 'name', result = {}) {
arr.forEach(item => {
// 使用传入的字段名获取键和值
result[item[keyField]] = item[valueField];
if (item.children && Array.isArray(item.children)) {
flattenToCustomKeyValue(item.children, keyField, valueField, result);
}
});
return result;
}
// 调用示例:假设键用code字段,值用label字段
const customData = [
{
code: 'A',
label: '一级分类',
children: [{ code: 'A1', label: '二级分类' }]
}
];
const customResult = flattenToCustomKeyValue(customData, 'code', 'label');
console.log(customResult);
// 输出:{ A: '一级分类', A1: '二级分类' }注意事项
处理数据前建议先判断输入是否为合法数组,避免非数组入参导致的报错,可添加类型校验逻辑。
如果数据源中可能存在循环引用(比如children引用了父级对象),递归或栈遍历都会陷入死循环,需要额外添加已访问元素缓存来处理这种情况。
若需要保留原始元素的所有字段,也可以将值设置为整个元素对象,只需要调整赋值逻辑为
result[item.id] = item即可。