js 怎么实现数组扁平化

数组扁平化是将多层嵌套数组转化为一层数组,常用方法包括ES2019的flat()、递归、reduce结合递归及迭代法;flat()性能好且简洁,适合现代环境,递归灵活但可能栈溢出,迭代法可避免栈溢出,适用于深度嵌套场景。

js 怎么实现数组扁平化

JavaScript数组扁平化,简单来说,就是把一个多层嵌套的数组(也就是二维、三维甚至更多维的数组)转换成一个只有一层的数组。核心思路无非就是遍历数组的每个元素,如果遇到还是数组的元素,就得想办法把它“展开”,直到所有元素都不再是数组为止。这在处理某些后端返回的复杂数据结构,或者需要对所有数据进行统一操作时,显得尤为重要。

解决方案

要实现数组扁平化,其实有几种常见的思路,每种都有其适用场景和一些小考量。

1. 使用

Array.prototype.flat()

方法 (ES2019+):这是最直接、最现代的方式,如果你的目标环境支持ES2019或更高版本,这几乎是首选。

const nestedArray = [1, [2, 3], [4, [5, 6, [7, 8]]], 9];// 扁平化一层const flatOnce = nestedArray.flat();console.log(flatOnce); // 输出: [1, 2, 3, 4, [5, 6, [7, 8]], 9]// 扁平化指定深度const flatTwoLevels = nestedArray.flat(2);console.log(flatTwoLevels); // 输出: [1, 2, 3, 4, 5, 6, [7, 8], 9]// 扁平化所有层级(Infinity 表示任意深度)const flatAll = nestedArray.flat(Infinity);console.log(flatAll); // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]
flat()

方法非常方便,它的参数

depth

决定了扁平化的层级。默认是

1

。如果传入

Infinity

,它会递归地扁平化所有嵌套数组。

2. 递归实现:这是理解扁平化原理最基础的方式,即便有了

flat()

,了解递归实现也很有价值。

function flattenDeepRecursive(arr) {  let result = [];  arr.forEach(item => {    if (Array.isArray(item)) {      // 如果是数组,递归调用自身,并将结果合并到当前结果数组      result = result.concat(flattenDeepRecursive(item));    } else {      // 如果不是数组,直接添加到结果数组      result.push(item);    }  });  return result;}const nestedArray = [1, [2, 3], [4, [5, 6, [7, 8]]], 9];const flatArray = flattenDeepRecursive(nestedArray);console.log(flatArray); // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]

这个递归版本非常直观,遇到数组就“钻进去”继续扁平化,直到遇到非数组元素才“吐出来”。

3. 使用

reduce

结合递归:这是一种更函数式编程风格的实现,利用

reduce

的累加特性。

function flattenWithReduce(arr) {  return arr.reduce((acc, val) => {    // 如果当前值是数组,就递归扁平化它,然后和累加器合并    // 否则,直接将当前值添加到累加器    return acc.concat(Array.isArray(val) ? flattenWithReduce(val) : val);  }, []); // 初始累加器为空数组}const nestedArray = [1, [2, 3], [4, [5, 6, [7, 8]]], 9];const flatArray = flattenWithReduce(nestedArray);console.log(flatArray); // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]

这个方法写起来很简洁,对熟悉

reduce

的开发者来说,可读性也很好。

为什么我们需要数组扁平化?它解决了什么实际问题?

我个人觉得,扁平化数组的需求往往源于数据结构和业务逻辑之间的“不对称”。很多时候,后端接口为了表达数据之间的层级关系,会返回多层嵌套的数据,这在概念上很清晰。但到了前端,尤其是需要展示一个列表、进行统一搜索、或者对所有叶子节点进行某种计算时,这种嵌套结构反而成了障碍。

比如,你可能从API得到一个复杂的菜单结构,里面有主菜单、子菜单、再子菜单,但现在产品经理说:“我们要做一个全局搜索,能搜到所有菜单项,不管它藏得多深。”这时候,你拿着一个多维数组去遍历查找,显然不如把它先扁平化成一个一维数组来得痛快。

再举个例子,假设你要统计一个组织架构里所有员工的工龄总和,而这个组织架构也是按部门、小组层层嵌套的。如果不对数据进行扁平化处理,你得写好几层循环才能触及到所有员工数据,而扁平化后,你只需要一次遍历就能搞定。

对我来说,扁平化更多是为了简化后续的数据处理逻辑。当数据“平”了,很多算法和操作就变得直观、线性,减少了处理层级嵌套带来的心智负担和代码复杂度。

不同的扁平化方法在性能和适用场景上有什么区别

在选择扁平化方法时,性能和适用场景是两个绕不开的话题。

Array.prototype.flat()

是现代浏览器内置的方法,通常是用C++等底层语言实现的,所以它的性能通常是最好的。它简洁、高效,而且能够指定扁平化的深度,或者直接用

Infinity

扁平化所有层级。它的主要“缺点”可能就是兼容性问题了,如果你的项目需要支持IE或者非常老的浏览器环境,那可能需要引入Polyfill或者选择其他方案。对我而言,如果项目允许,我肯定优先用它,因为它代码量最少,也最符合“JS原生能力”的理念。

递归实现的方案,包括我们自己手写的循环递归或者

reduce

结合递归的方案,它们的兼容性非常好,几乎在所有JavaScript环境中都能运行。它们也提供了最大的灵活性,比如你可以在递归过程中加入额外的逻辑,比如只扁平化特定类型的元素,或者在扁平化的同时进行数据转换。然而,递归有一个潜在的陷阱:栈溢出(Stack Overflow)。如果你的数组嵌套层级非常深(比如几千层),每次函数调用都会在调用栈上创建一个新的帧,当调用栈深度超过JavaScript引擎的限制时,就会抛出错误。在性能上,纯JavaScript的递归通常不如原生的

flat()

方法。

那么,什么时候选哪个呢?

优先

flat()

当你不需要处理非常老的浏览器,或者已经有Babel等工具进行转译时,这是最推荐的。它简单、性能好。选择递归/

reduce

递归: 当你需要处理旧环境兼容性,或者需要在扁平化过程中加入自定义逻辑时。但要警惕超深嵌套数组带来的栈溢出风险考虑迭代(非递归)方案: 如果你确定会遇到深度未知且可能非常深的嵌套数组,并且想避免栈溢出,那么基于循环和栈(或队列)的迭代扁平化方案会是更健壮的选择。虽然实现起来比递归稍微复杂一点,但它能有效规避栈溢出的问题。

扁平化过程中可能遇到哪些陷阱或挑战?如何避免?

在实际操作中,扁平化并不是简单的“一键到底”,有些细节和挑战需要留意。

一个最典型的挑战就是前面提到的“栈溢出”。当你使用递归方法去扁平化一个嵌套层级极深的数组时,比如一个数组里套着一个数组,这个数组又套着一个数组,层层叠叠几千上万层,JavaScript引擎的调用栈是有限的,很快就会因为递归调用层数过多而耗尽,抛出

RangeError: Maximum call stack size exceeded

错误。

如何避免栈溢出?最有效的方法就是使用迭代(非递归)的扁平化算法。这种方法通常会借助一个栈(或者队列)来模拟递归过程,但避免了实际的函数调用栈累积。

一个简单的迭代扁平化实现思路:

function flattenIterative(arr) {  const result = [];  const stack = [...arr]; // 将初始数组的所有元素放入栈中  // 当栈不为空时,持续处理  while (stack.length > 0) {    const item = stack.shift(); // 取出栈顶元素(这里用shift模拟队列,保证顺序)    if (Array.isArray(item)) {      // 如果是数组,将其所有子元素(展开后)放回栈的头部      // 这样可以确保子数组的元素在当前层级的其他元素之前被处理      stack.unshift(...item);    } else {      // 如果不是数组,就添加到结果数组      result.push(item);    }  }  return result;}const deepNestedArray = [1, [2, [3, [4, [5, [6, [7, [8, [9, [10, /* ... 很多层 */]]]]]]]]]]];// 假设这里 deepNestedArray 嵌套了上千层// const flatArray = flattenDeepRecursive(deepNestedArray); // 这可能会栈溢出const flatArray = flattenIterative(deepNestedArray); // 这个通常不会console.log(flatArray); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]

这种迭代方式,无论数组嵌套多深,都不会导致栈溢出,因为它只依赖于循环和数组操作,而不是函数调用栈。

其他挑战:

非数组元素混入: 有时候数组里不仅有嵌套数组,还可能混入了

null

,

undefined

,

Set

,

Map

或者普通对象。

flat()

方法只会扁平化数组,其他类型的复杂对象会原样保留。如果你需要对这些非数组的复杂对象也进行某种“扁平化”(比如提取它们内部的值),那你的自定义扁平化逻辑就需要额外的判断和处理。性能瓶颈: 即使是迭代方法,对于包含数百万甚至上亿元素的巨大数组,扁平化操作本身仍然是计算密集型的。在处理超大规模数据时,需要考虑是否真的需要一次性完全扁平化,或者是否可以分批处理、懒加载,甚至在数据源头就进行预处理。原始数据结构语义丢失: 扁平化操作会彻底抹平数组的层级结构。如果原始的嵌套结构本身携带着重要的语义信息(比如父子关系、层级深度),那么扁平化后这些信息就丢失了。在这种情况下,你可能需要考虑在扁平化时额外记录这些信息(比如转换为带有

depth

属性的对象数组),或者重新审视扁平化是否是最佳方案。有时候,保留部分层级或者使用其他数据结构(如树形结构)会更合适。

以上就是js 怎么实现数组扁平化的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1515507.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月20日 09:09:54
下一篇 2025年12月20日 09:10:04

相关推荐

  • HTML和XML的区别分析

    在刚开始接触php或者HTML的时候,我们会遇到HTML和XML,可是它们之间有什么区别呢?本文我们主要和大家分享HTML和XML的区别分析,希望能帮助到大家。 一、HTML叫做超文本标记语言; xml是可扩展标记语言;它没有标签集(tagset),也没有语法规则(grammatical rule)…

    好文分享 2025年12月21日
    000
  • 为什么slot都是用在子组件

    这次给大家带来为什么slot都是用在子组件,使用slot子组件的注意事项有哪些,下面就是实战案例,一起来看一下。 使用slot场景一: 子组件Minput.vue  父组件 Minput  可以显示吗  这种情况下  Minput标签内的文字是不会渲染出来的 如果现在想在里面把文字渲染出来怎么办 好…

    2025年12月21日
    000
  • 详解浏览器渲染流程

    浏览器主要组件结构    (浏览器主要组件) 渲染引擎——webkit和Gecko Firefox使用Geoko——Mozilla自主研发的渲染引擎。 Safari和Chrome都使用webkit。Webkit是一款开源渲染引擎,它本来是为linux平台研发的,后来由Apple移植到Mac及Wind…

    2025年12月21日
    000
  • 为什么把css文件放在头部

    我们知道,在html文件中,我们一般都是把css放在头部,把js放在尾部,这是为什么呢?今天给大家好好分析一下这样做的原因。 这样会先加载css的样式,在渲染dom的时候已经知道了自己的样式了,所以一次渲染成功 如果css放在底部,那么需要先渲染dom,然后加载css后会重新渲染之前dom,需要两次…

    好文分享 2025年12月21日
    000
  • 为什么z-index会失效

    我们知道有时候在写代码的过程中会发现我写的z-index失效了,所以这次给大家带来为什么z-index会失效,使用为什么z-index的注意事项有哪些,下面就是实战案例,一起来看一下。 1、父标签 position属性为relative; 2、问题标签无position属性(不包括static); …

    好文分享 2025年12月21日
    000
  • 前端页面制作工具pagemaker详解

    pagemaker是一个前端页面制作工具,方便产品,运营和视觉的同学迅速开发简单的前端页面,从而可以解放前端同学的工作量。此项目创意来自网易乐得内部项目nfop中的pagemaker项目。原来项目的前端是采用jquery和模板ejs做的,每次组件的更新都会重绘整个dom,性能不是很好。因为当时rea…

    2025年12月21日 好文分享
    000
  • overflow的滚动有哪些重要性

    这次给大家带来overflow的滚动有哪些重要性,overflow的滚动重要性的注意事项有哪些,下面就是实战案例,一起来看一下。 原理 设置一个块级作用域溢出的效果,只需要在外部块的位置上设置overflow:scroll和height:xx即可。 此时,块级作用域的内容位移超出外部块的位移就会出现…

    好文分享 2025年12月21日
    000
  • div与span有什么区别

    这次给大家带来div与span有什么区别,div与span使用的注意事项有哪些,下面就是实战案例,一起来看一下。 共同点: DIV标签和SPAN标签是将一些内容当成一个整体进行处理,比如,整体隐藏,整体移动。类似一个盒子一样的东西。这样做可以精简代码,提高效率。 不同点: 1、div是将内容放到一个…

    好文分享 2025年12月21日
    000
  • html与xhtml和xml有什么区别

    这次给大家带来html与xhtml和xml有什么区别,html与xhtml和xml区别的注意事项有哪些,下面就是实战案例,一起来看一下。 发展趋势: html(超文本标记语言)——xhtml(可扩展性超文本标记语言)——xml(可扩展性标记语言); html: 1.对大小写不敏感; 2.标签不必成对…

    好文分享 2025年12月21日
    000
  • XML与HTML的区别

    相信有很多同学都搞不清楚html和xml到有什么区别对吧!今天就让我们看一下html和xml的区别在哪里,html和xml的语法有什么不同。 一、什么是HTML        带着疑问走到这里,一句话:HTML(HyperTextMark-upLanguage)即超文本标记语言,是WWW的描述语言。…

    好文分享 2025年12月21日
    000
  • 点击浏览器的返回按钮,就会刷新历史页面这个问题的解决方案

    首先我们知道这个问题是如何发生的,假如我们有如下页面列表信息页面,点击进入详情页面,在详情页面修改了数据通过历史返回,再返回到列表信息页面,因为列表信息是历史返回的,还是默认显示原来修改前的数据,要刷新一下才是修改后的数据,那么我们有什么办法可以点击手机返回按钮就可以刷新之前历史页面中的数据呢? o…

    好文分享 2025年12月21日
    000
  • 前端浏览器缓存怎么使用

    之前给大家介绍过浏览器的缓存,也介绍过html的离线缓存,说的太多反而容易弄混,今天就来给大家好好分辨一下这些缓存分别有什么区别以及怎么使用缓存。 200 from memory cache 不访问服务器,直接读缓存,从内存中读取缓存。此时的数据时缓存到内存中的,当kill进程后,也就是浏览器关闭以…

    好文分享 2025年12月21日
    000
  • html里的br,p和DIV有什么区别

    很多人弄不明白,br,p和div都是标签,他们之前有什么区别?我想怎么用都可以吗?并不是这样的,今天就来给大家详细的说一下br,p,div这三种标签有什么区别 div和p是成对组合闭合标签; 是单一的闭合标签。 以 开始, 结束; 以 开始, 立即学习“前端免费学习笔记(深入)”; 结束 小例: 内…

    好文分享 2025年12月21日
    000
  • 为什么要对DIV设置CSS样式?

    为什么要对div设置css样式?不设置样式不可以吗?设置了css样式对我们的网页有什么好处吗?我需要怎样布局? 回答这个问题首先我们要知道你网页想要什么样布局?这简单,我们是依据美工图进行布局的。然后就可以从这里开始理解,美工图是图片,而通过软件导出网页布局中所需图片素材后,在HTML使用HTML标…

    好文分享 2025年12月21日
    000
  • class与id有什么区别

    id与class有什么区别?id是什么意思?class是什么意思?今天我们就给大家介绍清楚这俩种容易混淆的概念 在div、span、p标签、h1、h2等标签中看见id和class使用,id和class是非常常用的标签内属性。 以上是我们常见看见id与class存在于div标签内。“header”和&…

    好文分享 2025年12月21日
    000
  • 为什么HTML网页乱码与解决方法

    有时候我们做出来的网页打开以后是乱码,那么我们需要怎么解决这种情况呢?以下就给大家带来乱码造成的原因以及解决乱码的方法。 一、乱码造成原因  1、比如网页源代码是gbk的编码,而内容中的中文字是utf-8编码的,这样浏览器打开即会出现html乱码。反之网页是编码utf-8,内容是gbk也会出现乱码。…

    好文分享 2025年12月21日
    000
  • 浏览器中关于标准模式与怪异模式的解析

    浏览器的标准模式和怪异模式到底是什么? 标准模式: 是浏览器按照w3c标准解析执行代码,这样用规定的语法去渲染,就可以兼容各个浏览器,保证以正确的形式展示网页。 怪异模式: 是使用浏览器自己的方式解析执行代码,因为不同浏览器解析执行的方式不一样,所以我们称之为怪异模式。 为什么还要存在怪异模式呢? …

    好文分享 2025年12月21日
    000
  • 认识与标签之间的区别

    br与p标签区别,br和p标签认识之p+css开发时候常用换行标签接下来,我们介绍下html中常遇见和常用到的和 标签 之间的区别及用法,以及使用css对他们控制设置属性样式——扩展知识css换行,css不换行。 一、综合介绍br p    –   TOP 首先,相同之处是br和p都是有…

    好文分享 2025年12月21日
    000
  • TCP和UDP的区别在哪

    tcp(transmission control protocol,传输控制协议)是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接。一个tcp连接必须要经过三次“对话”才能建立起来udp(user data protocol,用户数据报协议)是与tcp相对应的协议。它是面向非连…

    好文分享 2025年12月21日
    000
  • 如何解决IE8浏览器下dom元素不区分name属性大小写问题

    在ie8浏览器下用name属性去获取dom元素时居然是不区分大小写的。 比如: 如上有2个input输入框,它们的name属性分别是大写C1和小写c1 在获取元素时,在谷歌浏览器下使用jqury获取: $(“input[name=’c1′]”).length // 1 如上代码在I8下运行时获取的d…

    好文分享 2025年12月21日
    000

发表回复

登录后才能评论
关注微信