数组扁平化是js开发中处理复杂数据结构时的常见操作,核心目标是把包含多层嵌套元素的数组,转换为只有一层结构的一维数组,比如将[1, [2, [3, 4], 5]]转换为[1, 2, 3, 4, 5]。

什么是数组扁平化
数组扁平化也叫数组降维,当数组中的元素还是数组时,我们就称这个数组是多维数组。数组扁平化就是把这些嵌套的子数组全部展开,最终得到一个所有元素都是非数组类型的一维数组。在实际开发中,比如处理后端返回的嵌套结构数据、合并多个不同层级的数组时,经常会用到这个操作。
js实现数组扁平化的常见方法
1. 使用原生flat方法
ES6之后js数组新增了flat方法,专门用于实现数组扁平化,使用起来非常简单。flat方法可以接收一个参数,表示要扁平化的层级深度,默认是1,传入Infinity可以展开任意深度的嵌套数组。
// 基础使用,扁平化一层 const arr1 = [1, [2, 3], [4, [5, 6]]]; const result1 = arr1.flat(); console.log(result1); // 输出 [1, 2, 3, 4, [5, 6]] // 扁平化两层 const result2 = arr1.flat(2); console.log(result2); // 输出 [1, 2, 3, 4, 5, 6] // 扁平化任意深度 const arr2 = [1, [2, [3, [4, [5]]]]]; const result3 = arr2.flat(Infinity); console.log(result3); // 输出 [1, 2, 3, 4, 5]
这种方法的优点是代码简洁,不需要自己实现逻辑,但是需要注意兼容性,部分旧版本浏览器不支持flat方法,如果项目需要兼容旧环境,就不能直接使用这个方法。
2. 递归遍历实现
递归是实现数组扁平化的经典思路,核心逻辑是遍历数组的每一个元素,如果当前元素还是数组,就递归调用扁平化函数处理这个子数组,否则就把元素添加到结果数组中。
function flattenArray(arr) {
// 定义结果数组
const result = [];
// 遍历输入数组
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
// 判断当前元素是否为数组
if (Array.isArray(item)) {
// 如果是数组,递归处理,把结果合并到结果数组
result.push(...flattenArray(item));
} else {
// 如果不是数组,直接添加到结果数组
result.push(item);
}
}
return result;
}
// 测试示例
const testArr = [1, [2, [3, 4], 5], 6];
const flattenResult = flattenArray(testArr);
console.log(flattenResult); // 输出 [1, 2, 3, 4, 5, 6]
这种方法的兼容性很好,所有支持js的环境都可以运行,而且可以处理任意深度的嵌套数组,缺点是需要自己实现递归逻辑,代码量比flat方法稍多。
3. reduce方法结合递归实现
reduce方法是数组的高阶方法,可以对数组的每个元素执行回调函数,最终汇总为一个结果。我们可以结合reduce和递归来实现数组扁平化,代码会更简洁一些。
function flattenByReduce(arr) {
return arr.reduce((prev, curr) => {
// 判断当前元素是否为数组
if (Array.isArray(curr)) {
// 如果是数组,递归处理,然后合并到之前的累积结果
return prev.concat(flattenByReduce(curr));
} else {
// 如果不是数组,直接合并到累积结果
return prev.concat(curr);
}
}, []);
}
// 测试示例
const testArr2 = [1, [2, [3, [4]], 5]];
const reduceResult = flattenByReduce(testArr2);
console.log(reduceResult); // 输出 [1, 2, 3, 4, 5]
这种方法的逻辑和递归遍历类似,只是用reduce代替了手动遍历和结果数组的维护,代码更紧凑,同样支持任意深度的嵌套数组,兼容性也很好。
4. 扩展运算符结合some方法实现
我们还可以利用扩展运算符和some方法的特性来实现数组扁平化。some方法会检测数组中是否有元素满足指定条件,如果有就返回true。我们可以循环判断数组中是否还有数组元素,如果有就用扩展运算符把子数组展开,直到数组中没有子数组为止。
function flattenBySpread(arr) {
// 只要数组中还有数组元素,就继续循环
while (arr.some(item => Array.isArray(item))) {
// 用扩展运算符把数组展开,替换原数组
arr = [].concat(...arr);
}
return arr;
}
// 测试示例
const testArr3 = [1, [2, 3], [4, [5, 6]]];
const spreadResult = flattenBySpread(testArr3);
console.log(spreadResult); // 输出 [1, 2, 3, 4, 5, 6]
这种方法的思路比较巧妙,不需要显式的递归调用,但是循环的次数和数组的嵌套深度有关,原理上还是逐步展开子数组。
不同方法的对比
我们可以通过下面的表格对比几种方法的差异:
| 实现方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 原生flat方法 | 代码最简洁,使用简单 | 兼容性差,旧浏览器不支持 | 现代浏览器环境、不需要兼容旧项目的场景 |
| 递归遍历实现 | 兼容性极好,逻辑清晰 | 代码量稍多,深层嵌套可能有栈溢出风险 | 需要兼容旧环境、嵌套深度可控的场景 |
| reduce结合递归 | 代码紧凑,函数式风格 | 深层嵌套可能有栈溢出风险 | 偏好函数式编程、嵌套深度可控的场景 |
| 扩展运算符结合some | 思路巧妙,无显式递归 | 循环次数和嵌套深度相关,效率稍低 | 嵌套深度较浅、追求代码灵活性的场景 |
注意事项
在使用递归相关的扁平化方法时,如果数组的嵌套深度非常大,可能会出现栈溢出的问题,因为递归调用会占用调用栈空间。如果遇到超深层嵌套的数组,可以考虑使用迭代的方式实现扁平化,避免栈溢出。另外,如果数组中包含非数组的引用类型元素,比如对象,这些方法不会处理对象内部的数组属性,只会处理数组本身的直接嵌套元素。