一、背景与核心概念
什么是数组扁平化
在前端数据处理中,数组扁平化指的是将嵌套的数组结构转换为单层的一维数组。通过扁平化,可以让后续的去重、过滤、排序等操作变得更简单且可预测,代码可读性与可维护性显著提升。
常见情境包括接口返回的多级嵌套列表、表格数据中的嵌套行,以及图形或树形数据的节点提取。关键点在于准确识别哪些元素是数组,哪些是原始值,并按照设定的扁平化深度进行遍历与拼接。
二、常用实现方法
递归实现
递归实现是最直观的思路之一,通过对每个元素进行判断,若是数组则继续展开,否则将元素推入结果集合。优点是思路清晰、实现简洁,缺点在于深度较大时可能导致较深的调用栈,从而产生栈溢出风险。
实现要点包括使用一个外部结果集合、并在内部定义一个对当前子数组的递归函数,确保对非数组元素的处理是“直接添加”的而非再次遍历。
function flattenRecursive(arr) {const result = [];(function flat(sub) {sub.forEach(item => {if (Array.isArray(item)) flat(item);else result.push(item);});})(arr);return result;
}reduce 实现
使用 reduce 可以把当前项递归扁平化后拼接到累积结果中。这种写法在函数式编程风格中非常常见,但需要对中间数组的创建和内存开销保持关注,避免在大规模数据上产生性能瓶颈。合适的深度控制与缓存策略有助于提升稳定性。
核心思想是对每一项进行判断:若是数组则将其扁平化后再拼接,否则直接拼接当前值。通过递归调用实现对任意嵌套深度的处理。
function flattenReduce(arr) {return arr.reduce((acc, cur) => {if (Array.isArray(cur)) {return acc.concat(flattenReduce(cur));}return acc.concat(cur);}, []);
}三、 ES6+ 方案与技巧
flat 的深度参数
从 ES2019 引入的 Array.prototype.flat 可以直接对嵌套数组进行扁平化。深度参数 depth 指定展开的层级,Infinity 表示完全扁平化,而 1 仅展开一层。
在需要兼容旧浏览器或运行环境时,可以使用降级策略:若环境不支持 flat,则回退到自定义实现或组合其他方法实现类似效果。下面示例展示了如何结合 flat 与一个简单回退逻辑。
const data = [1, [2, [3, 4], 5], 6];
const completelyFlat = data.flat(Infinity); // [1, 2, 3, 4, 5, 6]
const shallowFlat = data.flat(1); // [1, 2, [3, 4], 5, 6]// 兼容性回退:如果没有 flat,使用自定义实现
if (typeof Array.prototype.flat !== 'function') {Array.prototype.flat = function(depth = 1) {const flatten = (arr, d) => arr.reduce((acc, val) => (Array.isArray(val) && d > 0 ? acc.concat(flatten(val, d - 1)) : acc.concat(val)), []);return flatten(this, depth);};
}
四、 兼容性与性能的折中
迭代实现与栈方法
当嵌套层级非常深时,递归方案可能触发调用栈的限制。迭代实现通过显式栈来遍历结构,避免深度不足的问题,同时对内存占用的预估也更可控。
下面给出一个基于栈的扁平化实现,它对任意嵌套结构都能正确处理,并尽量减少中间对象的创建。此方法的时间复杂度通常为 O(n),但需要额外的栈空间来缓存待处理的分支。
function flattenIterative(arr) {const stack = [arr];const result = [];while (stack.length) {const current = stack.pop();if (Array.isArray(current)) {for (let i = current.length - 1; i >= 0; i--) {stack.push(current[i]);}} else {result.push(current);}}return result;
}五、实战要点与案例
混合类型的扁平化处理
现实数据结构中往往包含数字、字符串,甚至对象。扁平化时应只展开数组,对非数组类型保持原样。通过严格的 Array.isArray 判断,可以避免将对象错误地当成数组。
完成扁平化后,常会在数据管线后段继续进行去重、筛选或类型转换,以保持数据的一致性与可控性。下面给出一个混合类型数组的示例。
const mixed = [1, [2, [3, 4]], 'a', [[5]], {x:1}];
const flattenNested = (arr) => arr.reduce((acc, v) => Array.isArray(v) ? acc.concat(flattenNested(v)) : acc.concat(v), []);
console.log(flattenNested(mixed)); // [1, 2, 3, 4, 'a', 5, {x:1}]
六、结合场景的快速示例
在数据处理流水线中的应用
在数据处理的流水线中,请求结果常常是多层嵌套的数组结构,需要统一成一维后再进行聚合。使用 flat(Infinity) 能快速简化分支结构,提升后续计算的性能与可读性。
在实际项目中,若目标环境不支持 flat,可以用前述自定义实现作为降级策略,同时在代码中保持对深度的控制,避免展开到不可控的层级。

// 模拟接口返回
const apiData = {data: {items: [1, [2, [3, 4]], 5],}
};
const arr = apiData.data.items;
const singleLayer = arr.flat(Infinity);
console.log(singleLayer); // [1, 2, 3, 4, 5]


