普通视图

发现新文章,点击刷新页面。
昨天 — 2025年7月3日首页

JavaScript 数据扁平化方法大全

作者 绅士玖
2025年7月3日 10:31

前言

数据扁平化是指将多维数组转换为一维数组的过程。由于嵌套数据结构增加了访问和操作数据的复杂度,所以·我们可以将嵌套数据变成一维的数据结构,下面就是我搜集到的一些方法,希望可以给你带来帮助!!

1. 使用 Array.prototype.flat()(推荐)

ES2019 引入的专门方法:

const nestedArr = [1, [2, [3, [4]], 5]];

// 默认只扁平化一层
const flattened1 = nestedArr.flat();
console.log(flattened1); // [1, 2, [3, [4]], 5]

// 指定深度为2
const flattened2 = nestedArr.flat(2);
console.log(flattened2); // [1, 2, 3, [4], 5]

// 完全扁平化
const fullyFlattened = nestedArr.flat(Infinity);
console.log(fullyFlattened); // [1, 2, 3, 4, 5]

解析

  • flat(depth) 方法创建一个新数组,所有子数组元素递归地连接到指定深度
  • 参数 depth 指定要提取嵌套数组的结构深度,可选的参数,默认为1
  • 使用 Infinity 可展开任意深度的嵌套数组,Infinity 是一个特殊的数值,表示无穷大

2. 使用 reduce() 和 concat() 递归

function flatten(arr) {
  // 使用 reduce 方法遍历数组元素
  return arr.reduce((acc, val) => {
    // 如果当前元素是数组,则递归调用 flatten 继续展开,并拼接到累积数组 acc
    if (Array.isArray(val)) {
      return acc.concat(flatten(val));
    } 
    // 如果当前元素不是数组,直接拼接到累积数组 acc
    else {
      return acc.concat(val);
    }
  }, []); // 初始累积值是一个空数组 []
}

// 测试用例
const nestedArr = [1, [2, [3, [4]], 5]];
console.log(flatten(nestedArr)); // 输出: [1, 2, 3, 4, 5]

解析

  1. 递归处理嵌套数组

    • 遇到子数组时,递归调用 flatten(val) 继续展开,直到所有层级都被展开为单层。
  2. reduce 方法的作用

    • 遍历数组,通过 acc(累积值)逐步拼接结果,初始值设为 [](空数组)。
  3. Array.isArray(val) 检查

    • 判断当前元素是否为数组,决定是否需要递归展开。
  4. concat 拼接结果

    • 将非数组元素或递归展开后的子数组拼接到累积数组 acc 中。

3. 使用 concat() 和扩展运算符递归

function flatten(arr) {
  // 使用扩展运算符 (...) 展开数组的第一层,并合并成一个新数组
  const flattened = [].concat(...arr);

  // 检查当前展开后的数组中是否仍然包含嵌套数组
  // 如果存在嵌套数组,则递归调用 flatten 继续展开
  // 如果所有元素都是非数组类型,则直接返回展开后的数组
  return flattened.some(item => Array.isArray(item)) 
    ? flatten(flattened) 
    : flattened;
}

// 测试用例
const nestedArr = [1, [2, [3, [4]], 5]];
console.log(flatten(nestedArr)); // 输出: [1, 2, 3, 4, 5]

解析

  1. [].concat(...arr) 展开一层数组

    • 使用扩展运算符 ... 展开 arr 的最外层,并通过 concat 合并成一个新数组。
    • 例如:[].concat(...[1, [2, [3]]]) → [1, 2, [3]](仅展开一层)。
  2. flattened.some(Array.isArray) 检查嵌套

    • 使用 Array.prototype.some() 检查当前数组是否仍然包含子数组。
    • 如果存在,则递归调用 flatten 继续展开。
  3. 递归终止条件

    • 当 flattened 不再包含任何子数组时,递归结束,返回最终结果。

4. 使用 toString() 方法(仅适用于数字数组)

const nestedArr = [1, [2, [3, [4]], 5]];
const flattened = nestedArr.toString().split(',').map(Number);
console.log(flattened); // [1, 2, 3, 4, 5]

解析

  1. toString() 的隐式转换

    • JavaScript 的 Array.prototype.toString() 会自动展开嵌套数组,并用逗号连接所有元素。
    • 例如:[1, [2, [3]]].toString() → "1,2,3"
  2. split(',') 分割字符串

    • 将字符串按逗号拆分成字符串数组,但所有元素会是字符串类型(如 "2")。
  3. map(Number) 类型转换

    • 通过 Number 构造函数将字符串元素转换为数字类型。
    • 注意:如果原数组包含非数字(如 ['a', [2]]),结果会变成 [NaN, 2]

优缺点

  • 优点:代码极其简洁,适合纯数字的嵌套数组。

  • 缺点

    • 仅适用于数字数组(其他类型会被强制转换,如 true → 1null → 0)。
    • 无法保留原数据类型(如字符串 '3' 会被转成数字 3)。

适用场景

  • 快速展开纯数字的嵌套数组,且不关心中间过程的性能损耗(toString 和 split 会有临时字符串操作)。

5. 使用 JSON.stringify() 和正则表达式

function flatten(arr) {
  // 1. 使用 JSON.stringify 将数组转换为字符串表示
  //    例如:[1, [2, [3]], 'a'] → "[1,[2,[3]],\"a\"]"
  const jsonString = JSON.stringify(arr);

  // 2. 使用正则表达式移除所有的 '[' 和 ']' 字符
  //    例如:"[1,[2,[3]],\"a\"]" → "1,2,3,\"a\""
  const withoutBrackets = jsonString.replace(/[\[\]]/g, '');

  // 3. 按逗号分割字符串,生成字符串数组
  //    例如:"1,2,3,\"a\"" → ["1", "2", "3", "\"a\""]
  const stringItems = withoutBrackets.split(',');

  // 4. 尝试将每个字符串解析回原始数据类型
  //    - 数字会变成 Number 类型(如 "1" → 1)
  //    - 字符串会保留(如 "\"a\"" → "a")
  //    - 其他 JSON 可解析类型也会被正确处理
  return stringItems.map(item => {
    try {
      // 尝试 JSON.parse 解析(处理字符串、数字等)
      return JSON.parse(item);
    } catch (e) {
      // 如果解析失败(如空字符串或非法 JSON),返回原始字符串
      return item;
    }
  });
}

// 测试用例
const nestedArr = [1, [2, [3, [4]], 5, 'a', { b: 6 }];
console.log(flatten(nestedArr)); 
// 输出: [1, 2, 3, 4, 5, "a", { b: 6 }]

解析

  1. JSON.stringify 的作用

    • 将整个数组(包括嵌套结构)转换为 JSON 字符串,保留所有数据类型信息。
  2. 正则替换 /[[]]/g

    • 移除所有方括号字符 [ 和 ],只保留逗号分隔的值。
  3. split(',') 分割字符串

    • 生成一个字符串数组,但每个元素可能仍是被 JSON 字符串化的(如 ""a"")。
  4. JSON.parse() 尝试恢复数据类型

    • 通过 JSON.parse 将字符串转换回原始类型(数字、字符串、对象等)。
    • 使用 try-catch 处理不合法的 JSON 字符串(如空字符串或格式错误的情况)。

优缺点

  • 优点

    • 支持任意数据类型(数字、字符串、对象等)。
    • 能正确处理嵌套对象(如 { b: 6 })。
  • 缺点

    • 性能较低(涉及 JSON 序列化、正则替换、解析等操作)。
    • 如果原始数组包含特殊字符串(如 "[1]" ,可能会被错误解析。

适用场景

  • 需要处理混合数据类型(非纯数字)的嵌套数组。
  • 对性能要求不高,但需要代码简洁的场景。

6. 使用堆栈的非递归实现

function flatten(arr) {
  // 创建栈并初始化(使用扩展运算符浅拷贝原数组)
  const stack = [...arr];
  const result = [];
  
  // 循环处理栈中的元素
  while (stack.length) {
    // 从栈顶取出一个元素
    const next = stack.pop();
    
    if (Array.isArray(next)) {
      // 如果是数组,展开后压回栈中(保持顺序)
      stack.push(...next);
    } else {
      // 非数组元素,添加到结果数组前端(保持原顺序)
      result.unshift(next);
    }
  }
  
  return result;
}

const nestedArr = [1, [2, [3, [4]], 5]];
console.log(flatten(nestedArr)); // [1, 2, 3, 4, 5]

解析

  1. 栈结构初始化

    • 使用扩展运算符 [...arr] 创建原数组的浅拷贝作为初始栈
    • 避免直接修改原数组
  2. 栈处理循环

    • 使用 while 循环处理栈直到为空
    • 每次从栈顶 pop() 一个元素进行处理
  3. 元素类型判断

    • 使用 Array.isArray() 检查元素是否为数组
    • 如果是数组则展开后重新压入栈
    • 非数组元素则添加到结果数组
  4. 顺序保持

    • 使用 unshift() 将元素添加到结果数组前端,当然这样比较费性能,可以改用 push() + reverse() 替代 unshift()
    • 确保最终结果的顺序与原数组一致

优缺点

  • 优点

    • 支持任意数据类型(不限于数字)
    • 可以处理深层嵌套结构(无递归深度限制)
    • 相比递归实现,不易导致栈溢出
  • 缺点

    • 使用 unshift() 导致时间复杂度较高(O(n²))
    • 需要额外空间存储栈结构
    • 相比原生 flat() 方法性能稍差
    • 无法控制扁平化深度(总是完全扁平化)

适用场景

  • 需要处理混合数据类型的深层嵌套数组
  • 需要避免递归导致的栈溢出风险

7. 使用 Array.prototype.some() 和扩展运算符

function flatten(arr) {
  // 循环检测数组中是否还包含数组元素
  while (arr.some(item => Array.isArray(item))) {
    // 使用扩展运算符展开当前层级的所有数组
    // 并通过concat合并为一层
    arr = [].concat(...arr);
  }
  return arr;
}

const nestedArr = [1, [2, [3, [4]], 5]];
console.log(flatten(nestedArr)); // [1, 2, 3, 4, 5]

解析

  1. 循环条件检测

    • 使用 arr.some() 方法检测数组中是否还存在数组元素
    • Array.isArray(item) 判断每个元素是否为数组
  2. 层级展开

    • 使用扩展运算符 ...arr 展开当前层级的数组
    • 通过 [].concat() 将展开的元素合并为新数组
  3. 迭代处理

    • 每次循环处理一层嵌套
    • 重复直到没有数组元素存在

性能比较

对于大多数现代应用:

  1. 优先使用 flat(Infinity)(最简洁且性能良好)
  2. 对于深度嵌套的大数组,考虑非递归的堆栈实现
  3. 递归方法在小数据集上表现良好且代码简洁
  4. 避免 toString() 方法除非确定只有数字数据

总结

JavaScript 提供了多种扁平化数组的方法,从简单的内置 flat() 方法到各种手动实现的递归、迭代方案。选择哪种方法取决于:

  • 运行环境是否支持 ES2019+
  • 数据结构的复杂程度
  • 对性能的要求
  • 代码可读性需求

在大多数现代应用中,flat(Infinity) 是最佳选择,因为它简洁、高效且语义明确。

❌
❌