前端JS: 数组扁平化

37 阅读2分钟

JavaScript 数组扁平化实现详解

一、扁平化概念

数组扁平化是指将一个多维数组转换为一维数组的过程:

// 多维数组
const arr = [1, [2, [3, [4, 5]], 6], 7];

// 扁平化后
// [1, 2, 3, 4, 5, 6, 7]

二、原生方法(ES2019+)

1. Array.prototype.flat()

const arr = [1, [2, [3, [4, 5]], 6], 7];

// 默认只展开一层
console.log(arr.flat()); // [1, 2, [3, [4, 5]], 6, 7]

// 指定展开深度
console.log(arr.flat(2)); // [1, 2, 3, [4, 5], 6, 7]

// 完全展开(Infinity表示无限深度)
console.log(arr.flat(Infinity)); // [1, 2, 3, 4, 5, 6, 7]

// 移除空位
console.log([1, 2, , 3, 4].flat()); // [1, 2, 3, 4]

三、手动实现方法

1. 递归实现(基础版)

function flatten(arr) {
  let result = [];
  
  for (let i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      result = result.concat(flatten(arr[i]));
    } else {
      result.push(arr[i]);
    }
  }
  
  return result;
}

// 使用示例
const arr = [1, [2, [3, [4, 5]], 6], 7];
console.log(flatten(arr)); // [1, 2, 3, 4, 5, 6, 7]

2. 递归实现(可指定深度)

function flattenDepth(arr, depth = 1) {
  if (depth === 0) return arr.slice(); // 深度为0,直接返回副本
  
  let result = [];
  
  for (let i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i]) && depth > 0) {
      result = result.concat(flattenDepth(arr[i], depth - 1));
    } else {
      result.push(arr[i]);
    }
  }
  
  return result;
}

// 使用示例
const arr = [1, [2, [3, [4, 5]], 6], 7];
console.log(flattenDepth(arr, 1)); // [1, 2, [3, [4, 5]], 6, 7]
console.log(flattenDepth(arr, 2)); // [1, 2, 3, [4, 5], 6, 7]
console.log(flattenDepth(arr, Infinity)); // [1, 2, 3, 4, 5, 6, 7]

3. 使用reduce实现

function flattenReduce(arr) {
  return arr.reduce((result, current) => {
    return result.concat(
      Array.isArray(current) ? flattenReduce(current) : current
    );
  }, []);
}

// 带深度的reduce版本
function flattenReduceDepth(arr, depth = 1) {
  return depth > 0
    ? arr.reduce((acc, val) => 
        acc.concat(Array.isArray(val) 
          ? flattenReduceDepth(val, depth - 1) 
          : val
        ), [])
    : arr.slice();
}

4. 使用栈实现(非递归)

function flattenArray(arr) {
  const stack = [...arr]; // 将数组转为栈,初始放入所有元素
  const result = [];

  while (stack.length) {
    const item = stack.pop(); // 弹出栈顶元素
    if (Array.isArray(item)) {
      // 如果是数组,将其元素按原顺序推入栈中
      for (let i = item.length - 1; i >= 0; i--) {
        stack.push(item[i]);
      }
    } else {
      result.push(item);
    }
  }

  return result.reverse(); // 由于栈是后进先出,需要反转恢复原顺序
}

// 使用示例
const nestedArr = [1, [2, [3, 4], 5], 6];
console.log(flattenArray(nestedArr)); // [1, 2, 3, 4, 5, 6]

5. 使用toString()方法

function flattenToString(arr) {
  return arr.toString()
    .split(',')
    .map(item => {
      // 转换回适当的数据类型
      const num = Number(item);
      return isNaN(num) ? item : num;
    });
}

// 注意:这种方法会将所有元素转为字符串再解析
// 只适用于纯数字数组或可转换为字符串的元素
const arr = [1, [2, [3, [4, 5]], 6], 7];
console.log(flattenToString(arr)); // [1, 2, 3, 4, 5, 6, 7]

// 局限性示例
const mixedArr = [1, [2, ['a', ['b', 'c']]], 3];
console.log(flattenToString(mixedArr)); // [1, 2, 'a', 'b', 'c', 3]

总结

推荐方法选择

  1. 现代项目(支持ES2019+) :直接使用arr.flat(Infinity)
  2. 需要深度控制:使用递归版本flattenDepth
  3. 大数组或性能敏感:使用栈实现的非递归版本
  4. 需要处理循环引用:使用flattenSafe或完整版
  5. 简单场景:使用reduce或递归基础版

注意事项

  • 方法选择要考虑浏览器兼容性
  • 递归方法可能导致栈溢出(深度过大)
  • 字符串转换方法有类型丢失问题
  • 注意处理稀疏数组和循环引用
  • 性能测试显示原生flat通常最快,栈实现次之