很多时候我们需要处理嵌套数组,但环境不支持 flat() 或者想更精细地控制过程。核心思路其实就两点:遍历元素,遇到数组就继续拆解,否则保留。下面分享三种落地方案,从易到难,覆盖不同场景。
递归实现(最直观,适合常规场景)
递归的核心在于'自调用'。遍历数组的每一个元素,如果当前项是数组且未达到指定深度,就再次调用自身;如果不是数组,直接加入结果。这种方式代码可读性最高,新手最容易上手。
function flattenArray(arr, depth = Infinity) {
const result = [];
arr.forEach(item => {
if (Array.isArray(item) && depth > 0) {
result.push(...flattenArray(item, depth - 1));
} else {
result.push(item);
}
});
return result;
}
// 测试:完全扁平化
console.log(flattenArray([1, [2, [3]], 4]));
// 输出:[1, 2, 3, 4]
这里有个细节要注意:depth 参数控制了扁平化的层级,默认 Infinity 表示彻底打平。配合扩展运算符 ...,可以把递归返回的子数组元素逐个展开,避免二次嵌套。
迭代实现(防栈溢出,适合深层嵌套)
如果数组嵌套极深,递归可能会触发调用栈溢出错误。这时候用循环加栈的方式更稳妥。利用栈'后进先出'的特性,把待处理的子数组压入栈中,直到栈空为止。
function flattenArrayIterative(arr) {
const stack = [...arr];
const result = [];
while (stack.length) {
const item = stack.pop();
if (Array.isArray(item)) {
stack.push(...item);
} else {
result.push(item);
}
}
return result.reverse();
}
// 测试
console.log(flattenArrayIterative([1, [2, [3, 4], 5]]));
// 输出:[1, 2, 3, 4, 5]
因为 pop 是从末尾取元素,为了保持原数组顺序,最后需要反转一次结果。虽然多了一步操作,但避免了递归深度的限制,处理超深嵌套更安全。
Reduce 简化写法(函数式风格)
喜欢函数式编程的话,可以用 reduce 替代显式的循环和结果数组声明。逻辑和递归法一致,只是写法更紧凑。
function flattenArrayReduce(arr, depth = Infinity) {
return arr.reduce((acc, item) => {
if (Array.isArray(item) && depth > 0) {
return acc.concat(flattenArrayReduce(item, depth - 1));
} else {
return acc.concat(item);
}
}, []);
}
// 测试
console.log(flattenArrayReduce([1, [2, [3]], 4]));
// 输出:[1, 2, 3, 4]
acc 就是累计的扁平化数组,concat 负责合并。这种写法在代码量上最省,但在性能极端场景下可能不如前两种灵活。
总结
不用 flat() 也能搞定数组扁平化,关键在于理解'拆解'的逻辑。
- 递归法:代码简单,适合大多数情况。
- 迭代法:无栈溢出风险,适合深层数据。
- Reduce 法:简洁优雅,适合函数式偏好者。
实际开发中,可以根据数据嵌套深度和团队编码规范来选择。如果需要精确控制扁平化层数,记得加上 depth 参数,这样就能完美模拟 flat(depth) 的行为。

