2025年9月25日 · 前端进阶 · 手撕代码系列
在前端开发中,数组扁平化 是一个高频面试题,几乎每一场大厂面试都会被问到:"如何将一个多维数组变成一维数组?"
今天我们就来彻底搞懂这个问题,从递归、reduce、栈模拟到 ES6 新特性,5种主流实现方式一次性讲透,助你轻松应对面试官的灵魂拷问!
🧠 什么是数组扁平化?
数组扁平化(Flatten) 就是把一个嵌套多层的数组转换为只有一层的一维数组。
例如:
js
[1, [2, [3, 4]], 5] → [1, 2, 3, 4, 5]
看似简单,但背后考察的是你对 递归、高阶函数、数据结构 的理解深度。
✅ 方法一:递归 + concat(最经典)
这是最直观、最容易理解的方法,也是面试官最希望听到的"基础解法"。
js
function flatten(arr) {
let res = [];
for (let item of arr) {
if (Array.isArray(item)) {
// 如果是数组,递归展开并 concat 到结果中
res = res.concat(flatten(item));
} else {
// 否则直接 push
res.push(item);
}
}
return res;
}
✅ 核心逻辑:
- 递归入口:遇到数组就继续 flatten
- 递归出口:遇到非数组项,直接返回
- 拼接方式 :使用
concat
合并子数组
⚠️ 缺点:
concat
每次都会创建新数组,性能较差(尤其深层嵌套时)- 递归调用栈可能溢出(极端情况)
✅ 方法二:reduce + 递归(函数式编程风)
利用 reduce
累加器思想,更符合函数式编程风格,代码更简洁。
js
const flatten = arr =>
arr.reduce((acc, cur) =>
acc.concat(Array.isArray(cur) ? flatten(cur) : cur), []);
✅ 优点:
- 一行代码搞定,优雅简洁
- 函数式思维清晰
⚠️ 缺点:
- 依然存在
concat
性能问题 - 可读性略低于 for 循环版本
✅ 方法三:栈模拟(避免递归爆栈)
用栈(Stack) 模拟递归过程,避免深层递归导致的调用栈溢出。
js
function flatten(arr) {
const stack = [...arr]; // 模拟调用栈
const res = [];
while (stack.length) {
const item = stack.pop(); // LIFO:后进先出
if (Array.isArray(item)) {
// 是数组,将其元素重新压入栈
stack.push(...item);
} else {
// 非数组,直接加入结果
res.push(item);
}
}
return res.reverse(); // 因为是倒序出栈,所以需要反转
}
✅ 优点:
- 避免递归爆栈,适合处理超深嵌套
- 时间复杂度稳定,性能优于递归
- 体现对数据结构的理解
💡 图解栈过程:
js
初始栈: [1, [2, [3, 4]], 5]
→ pop 5 → push 5 → res=[5]
→ pop [2, [3,4]] → push 2, [3,4] → 栈变为 [1, 2, [3,4]]
→ pop [3,4] → push 3,4 → 栈变为 [1,2,3,4]
→ pop 4,3,2,1 → res=[5,4,3,2,1] → reverse → [1,2,3,4,5]
✅ 方法四:ES6 flat()
API(现代写法)
ES2019 引入了原生 flat()
方法,一行代码解决战斗!
js
console.log([1, [2, [3, 4]], 5].flat(Infinity));
// 输出: [1, 2, 3, 4, 5]
✅ 优点:
- 语法极简,语义清晰
- 性能优秀,由引擎优化
- 支持指定扁平化深度:
flat(1)
、flat(2)
等
⚠️ 注意:
flat(Infinity)
可以完全扁平化任意层数- 需注意浏览器兼容性(IE 不支持)
✅ 方法五:toString + split(取巧但有限制)
利用数组 toString
会自动扁平化的特性:
js
function flatten(arr) {
return arr.toString().split(',').map(Number);
}
✅ 优点:
- 极简!一行搞定
- 适合纯数字数组
⚠️ 缺点:
- 只适用于数字数组,字符串会出错
null
、undefined
、对象等会变成"null"
字符串- 类型转换不安全
❌ 不推荐用于生产环境,仅作"骚操作"了解。
📊 五种方法对比总结
方法 | 是否递归 | 是否安全 | 性能 | 推荐指数 |
---|---|---|---|---|
递归 + concat | ✅ | ✅ | ⭐⭐ | ⭐⭐⭐ |
reduce + 递归 | ✅ | ✅ | ⭐⭐ | ⭐⭐⭐⭐ |
栈模拟 | ❌ | ✅ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
flat(Infinity) |
❌ | ✅ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
toString + split | ❌ | ❌ | ⭐⭐⭐ | ⭐ |
💡 面试加分技巧
-
主动说明边界情况:
jsflatten([1, [2, [3, [4, [5]]]], 6]) // 深层嵌套 flatten([1, null, undefined, [2]]) // null/undefined flatten([]) // 空数组
-
扩展:支持 depth 参数
jsfunction flatten(arr, depth = Infinity) { if (depth === 0) return arr; let res = []; for (let item of arr) { if (Array.isArray(item)) { res.push(...flatten(item, depth - 1)); } else { res.push(item); } } return res; }
-
说出
flat()
的 polyfill 思路"现代项目我推荐用
flat()
,但如果要兼容旧环境,我会用栈模拟实现一个 polyfill。"
✅ 总结:面试如何回答?
面试官:请手写一个数组扁平化函数
✅ 标准回答结构:
- 定义问题:先说明什么是扁平化
- 给出基础解法:递归 + isArray
- 优化方案:提到 reduce 和栈模拟
- 现代方案:flat() 是首选
- 扩展思考:兼容性、性能、边界
"我会优先使用
Array.prototype.flat(Infinity)
,因为它简洁高效。如果需要兼容老环境,我会用栈模拟避免递归爆栈,同时保证性能稳定。"
📣 结语
数组扁平化虽小,却能全面考察你的编程思维。掌握这 5 种写法,不仅能轻松应对面试,更能提升你对 递归、函数式编程、数据结构 的理解。
🔖 收藏 + 转发,下次面试前翻出来看一眼,稳了!