前端JS: 数组扁平化

JavaScript 数组扁平化实现详解

一、扁平化概念

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

arduino 复制代码
// 多维数组
const arr = [1, [2, [3, [4, 5]], 6], 7];

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

二、原生方法(ES2019+)

1. Array.prototype.flat()

arduino 复制代码
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. 递归实现(基础版)

ini 复制代码
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. 递归实现(可指定深度)

ini 复制代码
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实现

scss 复制代码
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. 使用栈实现(非递归)

ini 复制代码
function flattenStack(arr) {
  const stack = [...arr];
  const result = [];
  
  while (stack.length) {
    const next = stack.pop();
    
    if (Array.isArray(next)) {
      // 将数组元素推入栈中(注意保持顺序)
      stack.push(...next.slice().reverse());
    } else {
      result.push(next);
    }
  }
  
  return result.reverse();
}

// 优化版本(保持顺序)
function flattenStackOrdered(arr) {
  const stack = [];
  const result = [];
  let current = arr;
  let i = 0;
  
  while (current !== undefined) {
    if (i < current.length) {
      const item = current[i];
      i++;
      
      if (Array.isArray(item)) {
        // 保存当前状态
        stack.push({ current, i });
        // 切换到子数组
        current = item;
        i = 0;
      } else {
        result.push(item);
      }
    } else if (stack.length > 0) {
      // 恢复上一个状态
      const saved = stack.pop();
      current = saved.current;
      i = saved.i;
    } else {
      current = undefined;
    }
  }
  
  return result;
}

5. 使用toString()方法

javascript 复制代码
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通常最快,栈实现次之
相关推荐
比特鹰2 小时前
手把手带你用Flutter手搓人生K线
前端·javascript·flutter
奔跑路上的Me2 小时前
前端导出 Word/Excel/PDF 文件
前端·javascript
bluceli2 小时前
JavaScript异步编程深度解析:从回调到Async Await的演进之路
前端·javascript
SuperEugene2 小时前
路由与布局骨架篇:布局系统 | 头部、侧边栏、内容区、面包屑的拆分与复用
前端·javascript·vue.js
代码煮茶2 小时前
前端网络请求实战 | Axios 从入门到封装(拦截器 / 错误处理 / 重试)
javascript
进击的尘埃2 小时前
组合式函数 Composables 的设计模式:如何写出可复用的 Vue3 Hooks
javascript
进击的尘埃2 小时前
浏览器渲染管线深度拆解:从 Parse HTML 到 Composite Layers 的每一帧发生了什么
javascript
大雨还洅下2 小时前
前端手写: Promise封装Ajax
javascript
codeniu2 小时前
@logicflow/vue-node-registry 在 Vite 中无法解析的踩坑记录与解决方案
前端·javascript