60 秒回答模板

数组拉平就是按原顺序遍历输入,遇到数组且当前 depth 还大于 0 就继续展开,否则把元素放进结果。实现时要明确 depth 默认值、depth 为 0 时保留当前结构、空数组如何处理,以及是否跳过稀疏数组空位。不要用 join/split,因为它会把元素转成字符串并丢失类型。深度特别大时,递归实现可能调用栈溢出,可以改成显式栈;生产中如果语义符合,优先使用原生 flat。

考点 核心机制与工程取舍
难度 中高频面试题
回答目标 按定义、机制、场景讲清楚

深入解析

01

函数契约

先定义 flatten(input, depth = Infinity) 返回一个新数组,不修改原数组。depth 表示最多展开几层,0 表示完全不展开,Infinity 表示尽可能展开所有嵌套数组。

02

递归主线

遍历数组每个元素:如果元素仍是数组且 depth > 0,就递归展开 depth - 1 层;否则直接加入结果。递归返回的元素要按顺序追加,保证输出顺序和从左到右读取一致。

03

类型不能丢

元素可能是数字、字符串、对象、null、undefined、Symbol 或函数。正确实现只判断 Array.isArray,不应把所有元素转成字符串,也不应通过 JSON 序列化处理。

04

稀疏数组边界

原生 flat 会跳过被展开层级里的空槽。手写时如果用 for...of 遍历会读出 undefined,语义可能和原生不同;如果要贴近原生,可用索引遍历并判断 i in arr。

05

栈深风险

递归写法清晰,但极深嵌套会占用调用栈。面试可先写递归版本,再补充可以用显式栈保存待处理元素和剩余 depth 来避免调用栈溢出。

javascript

支持 depth 的 flatten

function flatten(input, depth = Infinity) {
  const result = [];

  function walk(arr, restDepth) {
    for (let i = 0; i < arr.length; i += 1) {
      if (!(i in arr)) continue;

      const item = arr[i];
      if (Array.isArray(item) && restDepth > 0) {
        walk(item, restDepth - 1);
      } else {
        result.push(item);
      }
    }
  }

  walk(input, depth);
  return result;
}

易错点

  • 不要用 join/split 实现通用拉平,类型和空值语义都会丢。
  • 不要忘记 depth 为 0 的语义,它应该保留当前嵌套结构。
  • 不要默认所有类数组都当数组展开,题目没要求时只用 Array.isArray 更稳。
  • 不要忽略递归深度风险,手写实现后要能说明显式栈替代方案。

面试官追问

为什么不能用 join 再 split?

它会把元素变成字符串,null、undefined、对象、数组空位等语义都会变化,无法作为通用 flatten 实现。

depth 参数应该怎么处理?

每进入一层嵌套就减 1,depth 到 0 后不再展开当前数组,而是把它作为元素保留。默认可以设为 Infinity。

递归会有什么问题?

极深嵌套会导致调用栈溢出。可以用显式栈模拟递归,并保存元素和剩余可展开深度。

flat 和 flatMap 有什么区别?

flat 只展开已有数组;flatMap 先对每个元素执行映射函数,再展开一层,等价于 map 后接一层 flat。