一文解析JavaScript中数组扁平化的各种实现方法
作者:yyt_
在现代前端开发中,数组操作是日常编码中最常见的任务之一。而在处理复杂数据结构时,我们经常会遇到“嵌套数组”(即高维数组)的场景。例如,后端返回的数据结构可能是多层嵌套的,我们需要将其“拍平”为一维数组以便于渲染或进一步处理。这种将多层嵌套数组转换为单层数组的过程,就被称为 数组扁平化(Array Flattening)。
本文将带你全面了解 JavaScript 中数组扁平化的各种方法,包括原生 API 的使用、递归实现、reduce 高阶函数应用、利用 toString 和 split 的巧妙技巧,以及基于展开运算符的循环优化方案。我们将深入剖析每种方法的原理、优缺点和适用场景,帮助你构建完整的知识体系。
一、什么是数组扁平化
数组扁平化,顾名思义,就是把一个嵌套多层的数组“压平”成一个只有一层的一维数组。例如:
const nestedArr = [1, [2, 3, [4, 5]], 6]; // 扁平化后应得到: // [1, 2, 3, 4, 5, 6]
这个问题看似简单,但在实际项目中非常常见。比如你在处理树形菜单、评论回复结构、文件目录层级等数据时,都可能需要对嵌套数组进行扁平化处理。
二、使用原生flat()方法(推荐方式)
ES2019 引入了 Array.prototype.flat() 方法,使得数组扁平化变得极其简单和直观。
基本语法
arr.flat([depth])
depth:指定要展开的层数,默认为1。- 如果传入
Infinity,则无论嵌套多少层,都会被完全展开。
示例代码
const arr = [1, [2, 3, [1]]]; console.log(arr.flat()); // [1, 2, 3, [1]] → 只展开一层 console.log(arr.flat(2)); // [1, 2, 3, 1] → 展开两层 console.log(arr.flat(Infinity)); // [1, 2, 3, 1] → 完全展开
特点总结
- 简洁高效:一行代码解决问题。
- 兼容性良好:现代浏览器基本都支持(IE 不支持)。
- 可控制深度:灵活控制展开层级。
- 推荐用于生产环境:清晰、安全、性能好。
注意:flat() 不会改变原数组,而是返回一个新的扁平化数组。
三、递归实现:最经典的思路
如果你不能使用 flat()(比如兼容老版本浏览器),或者想深入理解其内部机制,那么递归是一个经典且直观的解决方案。
基础递归版本
function flatten(arr) {
let res = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
res = res.concat(flatten(arr[i])); // 递归处理子数组
} else {
res.push(arr[i]); // 非数组元素直接加入结果
}
}
return res;
}
// 测试
const arr = [1, [2, 3, [1]]];
console.log(flatten(arr)); // [1, 2, 3, 1]
分析
使用 for 循环遍历每个元素。
判断是否为数组:是 → 递归调用;否 → 直接推入结果数组。
利用 concat 合并递归结果。
缺点
每次 concat 都会创建新数组,性能略低。
递归深度过大可能导致栈溢出(极端情况)。
四、使用reduce+ 递归:函数式编程风格
利用 reduce 可以写出更优雅、更具函数式风格的扁平化函数。
实现方式
function flatten(arr) {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, []);
}
解析
reduce接收一个累加器pre和当前元素cur。- 如果
cur是数组,则递归调用flatten(cur),否则直接使用cur。 - 使用
concat将结果合并到pre中。
优点
- 代码简洁,逻辑清晰。
- 更符合函数式编程思想。
- 易于组合其他操作(如 map、filter)。
五、利用toString()+split()的“黑科技”技巧
这是一个非常巧妙但需要谨慎使用的技巧,适用于数组中只包含数字或字符串基本类型的情况。
实现原理
JavaScript 中,数组的 toString() 方法会递归地将每个元素转为字符串,并用逗号连接。
const arr = [1, [2, 3, [1]]]; console.log(arr.toString()); // "1,2,3,1"
我们可以利用这一点,先转成字符串,再用 split(',') 分割,最后通过 +item 转回数字。
实现代码
function flatten(arr) {
return arr.toString().split(',').map(item => +item);
}
// 测试
const arr = [1, [2, 3, [1]]];
console.log(flatten(arr)); // [1, 2, 3, 1]
优点
- 代码极短,实现“一行扁平化”。
- 性能较好(底层由引擎优化)。
缺点
- 仅适用于纯数字数组:如果数组中有字符串
"hello",+"hello"会变成NaN。 - 无法保留原始类型:所有元素都会被转为数字。
- 丢失
null、undefined、对象等复杂类型信息。
所以这个方法虽然巧妙,但不适合通用场景,仅作为面试中的“技巧”了解即可。
六、使用while循环 +concat+ 展开运算符(性能优化版)
这种方法避免了递归调用,采用循环逐步“拍平”数组,适合处理深层嵌套且希望避免栈溢出的场景。
实现方式
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
原理解析
arr.some(item => Array.isArray(item)):检查数组中是否还存在嵌套数组。...arr:展开数组的所有元素。[].concat(...arr):concat会对展开后的数组元素自动“拍平一层”。
举个例子:
[].concat(...[1, [2, 3, [1]]]) // 等价于 [].concat(1, [2, 3, [1]]) // → [1, 2, 3, [1]] → 拍平了一层
然后继续循环,直到没有嵌套为止。
优点
- 非递归,避免栈溢出。
- 逻辑清晰,易于理解。
- 性能较好,尤其适合中等深度嵌套。
缺点
- 每次
concat(...arr)都会创建新数组,内存开销较大。 - 对于极深嵌套,仍可能影响性能。
七、对比总结:各种方法的适用场景
| 方法 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|
| arr.flat(Infinity) | 简洁、标准、安全 | IE 不支持 | 生产环境首选 |
| 递归 + for | 逻辑清晰,易理解 | 性能一般,可能栈溢出 | 学习理解原理 |
| reduce + 递归 | 函数式风格,优雅 | 同上 | 偏好函数式编程 |
| toString + split | 代码短,性能好 | 类型受限,不通用 | 面试技巧 |
| while + concat + ... | 非递归,避免栈溢出 | 内存占用高 | 深层嵌套处理 |
八、扩展思考:如何实现深度可控的扁平化
有时候我们并不想完全拍平,而是只想展开指定层数。可以仿照 flat(depth) 实现一个通用函数:
function flattenDepth(arr, depth = 1) {
if (depth === 0) return arr.slice(); // 深度为0,直接返回副本
let result = [];
for (let item of arr) {
if (Array.isArray(item) && depth > 0) {
result.push(...flattenDepth(item, depth - 1));
} else {
result.push(item);
}
}
return result;
}
// 测试
const arr = [1, [2, 3, [4, 5, [6]]]];
console.log(flattenDepth(arr, 1)); // [1, 2, 3, [4, 5, [6]]]
console.log(flattenDepth(arr, 2)); // [1, 2, 3, 4, 5, [6]]
console.log(flattenDepth(arr, Infinity)); // [1, 2, 3, 4, 5, 6]
九、结语
小贴士:如果你的项目需要兼容老旧浏览器,可以使用 Babel 转译 flat(),或手动引入 polyfill:
// Polyfill for Array.prototype.flat
if (!Array.prototype.flat) {
Array.prototype.flat = function(depth = 1) {
return this.reduce((acc, val) =>
Array.isArray(val) && depth > 0
? acc.concat(val.flat(depth - 1))
: acc.concat(val)
, []);
};
}
这样就能在任何环境中愉快地使用 flat() 了!
到此这篇关于一文解析JavaScript中数组扁平化的各种实现方法的文章就介绍到这了,更多相关JavaScript数组扁平化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
