【前端面试小册】JS-第28节:实现 flatten(数组扁平化)
一、核心概念
1.1 什么是数组扁平化
数组扁平化是指将一个多维数组转换为一维数组的过程。
// 示例
[1, [2, 3], [4, [5, 6]]] // 多维数组
// 扁平化后
[1, 2, 3, 4, 5, 6] // 一维数组
1.2 应用场景
- 处理嵌套数据结构
- API 返回数据的处理
- 树形结构转一维数组
- 数据清洗和转换
二、基础实现
2.1 递归实现
const flatten = array => array.reduce((acc, cur) => {
return Array.isArray(cur) ? [...acc, ...flatten(cur)] : [...acc, cur];
}, []);
// 测试
const demo1 = [2, [3, 4]];
const demo2 = [2, [3, [4, 5]], [6]];
const d1 = flatten(demo1);
const d2 = flatten(demo2);
console.log(d1); // [ 2, 3, 4 ]
console.log(d2); // [ 2, 3, 4, 5, 6 ]
2.2 原理解析
执行流程:
graph TD
A[开始 flatten] --> B[遍历数组元素]
B --> C{是否为数组?}
C -->|是| D[递归调用 flatten]
C -->|否| E[添加到结果数组]
D --> F[展开递归结果]
F --> E
E --> G{是否遍历完?}
G -->|否| B
G -->|是| H[返回结果]
关键点:
- 使用
reduce遍历数组 - 使用
Array.isArray判断是否为数组 - 递归处理嵌套数组
- 使用展开运算符合并结果
三、多种实现方式
3.1 使用 reduce + 递归
function flatten(arr) {
return arr.reduce((acc, cur) => {
return acc.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, []);
}
3.2 使用 forEach + 递归
function flatten(arr) {
const result = [];
arr.forEach(item => {
if (Array.isArray(item)) {
result.push(...flatten(item));
} else {
result.push(item);
}
});
return result;
}
3.3 使用 while + 展开运算符
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
特点:
- 非递归实现
- 每次展开一层
- 直到没有嵌套数组
3.4 使用 flat 方法(ES2019)
// 原生方法(推荐)
const flatten = arr => arr.flat(Infinity);
// 测试
const arr = [1, [2, [3, [4]]]];
console.log(flatten(arr)); // [1, 2, 3, 4]
四、支持指定层级
4.1 递归版本
function flatten(arr, depth = Infinity) {
if (depth === 0) {
return arr.slice(); // 返回副本
}
return arr.reduce((acc, cur) => {
return acc.concat(
Array.isArray(cur) && depth > 0
? flatten(cur, depth - 1)
: cur
);
}, []);
}
// 测试
const arr = [1, [2, [3, [4]]]];
console.log(flatten(arr, 1)); // [1, 2, [3, [4]]]
console.log(flatten(arr, 2)); // [1, 2, 3, [4]]
console.log(flatten(arr, Infinity)); // [1, 2, 3, 4]
4.2 优化版本
function flatten(arr, depth = Infinity) {
return arr.reduce((acc, val) => {
if (Array.isArray(val) && depth > 0) {
return acc.concat(flatten(val, depth - 1));
} else {
return acc.concat(val);
}
}, []);
}
// 测试
const nestedArray = [1, [2, [3, 4], 5], 6];
console.log(flatten(nestedArray)); // 默认全部打平,输出 [1, 2, 3, 4, 5, 6]
console.log(flatten(nestedArray, 1)); // 打平一层,输出 [1, 2, [3, 4], 5, 6]
console.log(flatten(nestedArray, 2)); // 打平两层,输出 [1, 2, 3, 4, 5, 6]
五、性能优化
5.1 迭代版本(避免栈溢出)
function flatten(arr) {
const stack = [...arr];
const result = [];
while (stack.length > 0) {
const next = stack.pop();
if (Array.isArray(next)) {
stack.push(...next);
} else {
result.push(next);
}
}
return result.reverse();
}
优点:
- 避免递归导致的栈溢出
- 适合处理深度嵌套的数组
5.2 使用生成器(内存友好)
function* flattenGenerator(arr) {
for (const item of arr) {
if (Array.isArray(item)) {
yield* flattenGenerator(item);
} else {
yield item;
}
}
}
function flatten(arr) {
return [...flattenGenerator(arr)];
}
优点:
- 内存友好
- 支持延迟计算
- 可以处理超大数组
六、实际应用场景
6.1 处理 API 返回数据
// API 返回的嵌套数据
const apiData = [
{ id: 1, children: [
{ id: 2, children: [
{ id: 3 }
]}
]}
];
// 扁平化处理
function extractIds(data) {
return flatten(data.map(item => {
const ids = [item.id];
if (item.children) {
ids.push(...extractIds(item.children));
}
return ids;
}));
}
console.log(extractIds(apiData)); // [1, 2, 3]
6.2 树形结构转一维数组
function treeToArray(tree, childrenKey = 'children') {
return flatten(tree.map(node => {
const result = [node];
if (node[childrenKey] && node[childrenKey].length > 0) {
result.push(...treeToArray(node[childrenKey], childrenKey));
}
return result;
}));
}
// 测试
const tree = [
{ id: 1, name: 'A', children: [
{ id: 2, name: 'B', children: [
{ id: 3, name: 'C' }
]}
]}
];
console.log(treeToArray(tree)); // 扁平化后的数组
6.3 数据清洗
function cleanData(data) {
return flatten(data)
.filter(item => item != null)
.filter(item => item !== '');
}
// 测试
const dirtyData = [1, [2, null, [3, '', 4]], undefined, 5];
console.log(cleanData(dirtyData)); // [1, 2, 3, 4, 5]
七、边界情况处理
7.1 处理空数组
function flatten(arr) {
if (!Array.isArray(arr)) {
throw new TypeError('Argument must be an array');
}
if (arr.length === 0) {
return [];
}
return arr.reduce((acc, cur) => {
return acc.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, []);
}
7.2 处理稀疏数组
function flatten(arr) {
return arr.reduce((acc, cur) => {
// 跳过空位
if (cur === undefined && !(cur in arr)) {
return acc;
}
return acc.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, []);
}
7.3 处理循环引用
function flatten(arr, visited = new WeakSet()) {
if (visited.has(arr)) {
return []; // 避免循环引用
}
visited.add(arr);
return arr.reduce((acc, cur) => {
return acc.concat(
Array.isArray(cur) ? flatten(cur, visited) : cur
);
}, []);
}
八、面试要点总结
核心知识点
- 递归实现:使用
reduce+ 递归 - 层级控制:支持指定扁平化层级
- 性能优化:迭代版本避免栈溢出
- 边界处理:空数组、稀疏数组、循环引用
常见面试题
Q1: 如何实现数组扁平化?
答:可以使用递归 + reduce,判断元素是否为数组,如果是数组则递归处理,否则直接添加到结果数组。
Q2: 如何支持指定扁平化层级?
答:添加 depth 参数,每次递归时减 1,当 depth 为 0 时停止递归。
Q3: 如何避免栈溢出?
答:使用迭代版本,使用栈结构代替递归,或者使用生成器函数。
实战建议
- ✅ 理解递归和迭代的区别
- ✅ 掌握多种实现方式
- ✅ 注意性能优化和边界情况
- ✅ 在实际项目中合理选择实现方式
前端面试小册 文章被收录于专栏
每天更新3-4节,持续更新中... 目标:50天学完,上岸银行总行!

腾讯成长空间 5881人发布

