news 2026/10/2 3:58:29

Array.from、Array.of、new Array 三种数组创建方法的核心区别与选型

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Array.from、Array.of、new Array 三种数组创建方法的核心区别与选型

1. 引言:三个方法都在创建数组,行为却天差地别

先说一个真实场景。之前在公司做代码评审,有位同事写了一段new Array(3).map(() => 0),本意是想要一个长度为 3、元素全是 0 的数组。结果运行之后,数组还是空壳子,map 回调根本没执行。这种问题说大不大,但确实会让人在调试上浪费不少时间。后来我发现,很多前端开发对Array.from()、Array.of()、new Array()这三个 API 的理解停留在“都是创建数组”的层面,一旦往深了用,各种隐蔽差异就全冒出来了。

这三个方法确实经常被放在一起比较:new Array()是 ES3 时代就存在的构造器,Array.of()和Array.from()都是 ES6 新增的静态方法。但它们的定位完全不同——new Array()是构造函数,Array.of()是修正参数行为的数组构造器,Array.from()则是通用转换工具。如果你只是记结论,那大概率会在某个边界场景里翻车;如果你理解了各自的设计动机和内部规则,就能在任何场景下快速选出正确写法。

这篇文章我会结合实际踩坑经验,把三个方法的区别拆开讲清楚,包括稀疏数组、类数组对象、可迭代对象、映射函数这些绕不开的点,最后再给一套可以直接照着选型的方法论。

2. new Array():最老的构造器,行为怪癖也最多

2.1 单个数字参数:你得到的不是元素,而是数组长度

先看最基本的行为差异。new Array(3),很多人直觉上认为它会生成[3],但它的真实结果是一个长度为 3、内部没有任何实际元素的数组。在控制台里通常显示为[empty × 3],Node 环境里也会显示[ <3 empty items> ]。这里的“空位”和undefined完全不是一回事:空位意味着这个索引上根本不存在属性,而undefined是属性存在但值为undefined。

可能你会觉得这有什么大不了的,反正访问的时候不都是undefined吗?错了。正因为索引位置没有属性,很多数组方法在遍历时会直接跳过这些位置。比如new Array(3).map(x => 1),结果依然是[empty × 3],map 回调一次都不会执行;forEach、filter、reduce同样会跳过。我第一次踩这个坑是在写一个图表组件的数据初始化逻辑时,想着先用new Array(12)生成 12 个格子,然后再用 map 填数据,结果图表死活渲染不出来,控制台一打印才发现全是空壳。

那为什么大家还这么爱用new Array(n)?因为它和.fill()配合起来很好用。new Array(3).fill(0)能得到[0, 0, 0],这才是创建“固定长度密集数组”的经典写法。填完值之后,map、forEach 就能正常遍历了。

这里还要注意一个边界:new Array(3.5)会直接抛RangeError: Invalid array length。数组长度必须是合法的、范围在 0 到 2^32 - 2 之间的整数。这个坑虽然不常见,但偶尔会在动态计算长度的代码里冒出来。比如你根据某个除法结果取整时忘了Math.floor,长度变成小数,整个页面就直接崩了。

2.2 多个参数:行为和单参数完全不同

new Array(1, 2, 3)的结果是[1, 2, 3],这个符合直觉。但这里的规则切换非常诡异:只要参数数量大于等于 2,所有参数都会被当作数组元素处理,无论是不是数字。所以new Array(1, 2, 3)和new Array("1", "2", "3")行为是一致的——都是元素列表。真正特殊的只有“单个数字参数”这一种情况。

这种“一个参数一个规则,多个参数另一个规则”的设计是历史遗留。ES6 的Array.of()就是为了消除这个歧义而出现的。

多参数模式下还有一个容易被忽略的问题:如果你用new Array(5)创建一个稀疏数组,然后直接赋值某个位置的元素,比如arr[2] = "x",中间依然会有空位。这种数组在JSON.stringify时会表现得很有趣——空位会被序列化为null。也就是说JSON.stringify(new Array(3))的结果是"[null,null,null]",这很容易让你误以为数组里真的有三个null,其实原数组依然是空壳。我在处理接口数据回填时碰到过类似情况,排错时一度以为是数据源问题,最后才发现是空位序列化机制在作怪。

3. Array.of():为补漏而生的参数构造器

3.1 设计动机和基本规则

Array.of()的设计目标非常明确:消除new Array()的单参数歧义。只要是把参数传给Array.of(),不管是一个还是多个,不管是不是数字,都会作为数组元素处理。

对比一下就清楚了:

new Array(3); // [empty × 3] Array.of(3); // [3] Array.of(3, 4); // [3, 4] Array.of("3"); // ["3"] new Array("3"); // ["3"],单字符串参数会被当元素处理

Array.of(1.5)得到[1.5],不会像new Array(1.5)那样抛 RangeError。Array.of()不传参数时得到空数组[],也和new Array()一致。

从使用体验来看,Array.of()本质上就是把一组参数“收纳”成一个数组。它的语义比new Array()更可预测,所以如果你在一个函数里需要根据参数列表创建数组,又不希望参数个数影响结果,Array.of()就是最稳妥的选择。

3.2 类数组场景的隐藏价值

很多人以为Array.of()就是“用参数造个数组”,没什么技术含量。但它在某些场景里比Array.prototype.slice.call(arguments)更干净。ES6 之后我们通常用展开运算符[...arguments]把类数组对象转成数组,但在需要保留元素顺序、又不想引入额外语义时,Array.of.apply(null, arrayLike)这种写法依然有其价值。当然,现在更推荐直接Array.from()处理类数组对象,这一点我在下一节展开。

这里还有一个冷门但值得一提的机制:Array.of()会使用this作为构造函数来创建最终数组。这意味着如果你给Array.of指定一个数组子类作为this,结果就是那个子类的实例。

class MyArray extends Array {} const result = MyArray.of(1, 2, 3); console.log(result instanceof MyArray); // true

这个特性在日常业务代码里用到得很少,但它解释了为什么在规范层面Array.of()和new Array()不能被简单地互相替代。如果你需要写通用底层库或工具函数,这一点是值得留意的。

4. Array.from():真正的“全能转换器”

4.1 类数组对象:有 length 就能变成数组

Array.from()的第一大能力是把类数组对象转成真正的数组。类数组对象指的是“有length属性、索引从 0 开始可以通过下标访问元素”的对象。最典型的例子是arguments对象和 DOM 查询结果NodeList。

早期要把这些对象转成数组,主流写法是Array.prototype.slice.call(arguments),或者循环 push。现在用Array.from()一行搞定:

function test() { const args = Array.from(arguments); console.log(args); // [1, 2, 3] } test(1, 2, 3);

很多人对这个功能的理解止步于此,但实际上Array.from()对类数组对象的处理有一个关键规则:它会先读取对象的length,然后依次访问0到length - 1这些索引上的属性。就算某些索引上没有属性,也会在结果数组里补上undefined。这一点和稀疏数组的行为差异非常大。

举个例子,如果传入一个稀疏数组,Array.from()的结果是密集数组,空位全部被填充为undefined:

const sparse = [1, , 3]; // 索引 1 是空位 const dense = Array.from(sparse); console.log(1 in dense); // true console.log(dense[1]); // undefined

这在处理外部数据时特别有用。有时候你从某个旧接口收到一个坑坑洼洼的数组,直接用Array.from()过一遍,空位就变成了普通的undefined,后面遍历时逻辑更可控。

4.2 第二参数 mapFn:转换的同时顺便做映射

Array.from()的结构是Array.from(arrayLike, mapFn, thisArg)。第二个参数是一个映射函数,作用类似数组的map(),但实现机制不同:它是边取元素边映射,不会再创建一个中间数组。第三个参数thisArg可以指定映射函数内部this的指向。

这个特性让Array.from()在生成序列时非常好用。比如创建一个 0 到 9 的数字数组:

const nums = Array.from({ length: 10 }, (_, i) => i); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

这个写法替代了经典的三段式new Array(10).fill(0).map((_, i) => i)。后者要先创建数组、填充、再映射,多走了一步;前者直接用长度对象配合映射函数一次性搞定,可读性也更好。

映射函数还适合在转换过程中做格式整理。比如拿到的类数组对象里全是字符串数字,你想直接转成数字数组:

const arrLike = { 0: "10", 1: "20", 2: "30", length: 3 }; const nums = Array.from(arrLike, Number); // [10, 20, 30]

注意这里直接把Number作为映射函数传入,简洁又不失可读性。实际工作中我经常用类似手法把接口返回的数据对象“拍”成数组,省去多次循环。

4.3 可迭代对象:字符串、Set、Map、生成器

Array.from()的另一大能力是处理可迭代对象。只要对象具有[Symbol.iterator]方法,Array.from()就能把它完整展开成数组。

字符串是最常见的例子。Array.from("hello")能直接把字符串按字符拆开。但如果你用展开运算符[...str],其实也能做到。两者的区别主要体现在处理 Unicode 字符时:Array.from()和展开运算符都会按迭代器走,也就是按 Unicode 码点拆分,而不是按 UTF-16 编码单元拆。比如对于包含代理对字符的字符串,split("")会把字符劈成两半,而Array.from()会保持完整。

const str = "Ab𝌆fe"; console.log(str.split("")); // ['A', 'b', 乱掉的字符..., 'f', 'e'] console.log(Array.from(str)); // ['A', 'b', 完整字符, 'f', 'e']

虽然这里我没有给出具体乱码字符,但你可以放心:处理多字节字符时,优先用Array.from(),不要用split("")。

Set和Map也很典型。Array.from(new Set([1, 2, 3]))能把 Set 转成数组。Array.from(map)会得到键值对数组的数组。这个需求用展开运算符也能实现,但Array.from()在作为函数参数传递时更方便,因为它本身就是一个独立的函数,可以灵活传给其他高阶函数。

生成器对象同样支持。以前我写数据分页工具时,会用一个生成器按批次产出数据,然后用Array.from()一次性收集全部结果:

function* generate() { yield 1; yield 2; yield 3; } const arr = Array.from(generate()); // [1, 2, 3]

Array.from()会自动遍历迭代器直到结束。这也是它区别于new Array()和Array.of()的显著特征——后面两者根本不接受迭代器参数,传进去也只是当成单个元素。

4.4 对数组长度的特殊处理

Array.from()还有个容易被忽略的细节:它以length为准来遍历,而不是以对象实际拥有的属性数量为准。这意味着什么?意味着一个看起来“空无一物”的对象,只要长度大,就会生成一个很长的数组。

const fake = { length: 3 }; const result = Array.from(fake); // [undefined, undefined, undefined]

这一点我在前面提过,但它值得单独强调:Array.from()不检查对象里是否真的有对应索引,而是“按”长度逐一访问。这正是它能用来快速生成数字序列的原理。反过来说,如果你传入一个 length 非常大的对象,性能上会花很长时间,因为每个索引都会被访问一次。

5. 实战选型:一张表和三句口诀

5.1 核心差异对照表

把三个方法放在一起横向看,差异会清晰很多:

维度new Array()Array.of()Array.from()
本质构造函数静态构造方法静态转换方法
单个数字参数当作长度,生成稀疏数组当作元素当作类数组处理,无实际意义
多个参数全部当作元素全部当作元素参数被当作类数组对象,通常不用来传列表
支持类数组对象不支持不支持支持,按 length 遍历
支持可迭代对象不支持不支持支持,自动展开迭代器
支持映射函数不支持不支持支持,第二参数
空位处理保留空位无空位概念把空位填充为 undefined
子类定制通过 new 绑定通过 this 绑定通过 this 绑定
典型用途创建指定长度数组后 fill把少量参数收纳为数组转换类数组/可迭代对象并映射

这张表涵盖了大部分差异,但实际选型时,我建议你记住三句口诀:

  • 已知固定元素数量不多,优先用数组字面量[...],或者用Array.of()。
  • 想创建一个指定长度的数组,后续再用.fill()或.map()填值,用new Array(n)。
  • 手头有类数组对象、可迭代对象,或者需要边转换边映射,直接用Array.from()。

5.2 常见模式的具体替换

很多读者问过我:既然Array.from()功能这么强,是不是以后就不需要new Array和Array.of了?不是。它们各自有不可替代的使用场景。

生成数字序列,优先Array.from:

Array.from({ length: 5 }, (_, i) => i + 1); // [1, 2, 3, 4, 5]

创建定长全0数组,优先new Array(n).fill(0):

const arr = new Array(5).fill(0); // [0, 0, 0, 0, 0]

fill之后的数组是密集的,每个索引都有真实属性,后续一切数组方法都能正常使用。

把一组参数变成数组,用Array.of:

function collect(a, b, c) { return Array.of(a, b, c); }

如果这里用new Array(a, b, c),只要传入了单个数字参数,行为就会跑偏。比如collect(3),用new Array返回的是[empty × 3],而Array.of返回的是[3]。使用 API 时,不确定性是最大的敌人,Array.of正是为此而生。

5.3 引用陷阱:fill 和 Array.from 的差异

补充一个实战里很容易踩的坑:new Array(3).fill({})和Array.from({ length: 3 }, () => ({}))看起来都生成了三个对象元素,但它们有本质区别。

fill()是往每个位置填充“同一个引用”。也就是说,生成的三个元素是同一个对象的三份引用,不是三个独立对象。如果你修改其中一个,另外两个也会跟着变。而Array.from()的映射函数会为每个元素执行一次,每次返回一个新对象,所以是三个独立对象。

const a = new Array(3).fill({ count: 0 }); a[0].count++; console.log(a[1].count); // 1,被连带修改了 const b = Array.from({ length: 3 }, () => ({ count: 0 })); b[0].count++; console.log(b[1].count); // 0,互不影响

这个差异在处理表格数据、批量初始化复杂对象时非常关键。你要心里有数:什么时候需要共享对象,什么时候需要独立对象。

6. 常见误区与经验笔记

6.1 误把 Array.from 当数组构造器

一个高频误区是想用Array.from(3)生成[3]或长度为 3 的数组,结果完全不对。因为Array.from()接受的是类数组对象或可迭代对象,单个数字既不是可迭代对象,也没有length属性,最终会生成一个空数组[]。

Array.from(3); // [] Array.of(3); // [3] new Array(3); // [empty × 3]

如果你脑子里没有牢固的“转换器”定位,很容易在这种边界上翻车。我的习惯是:凡是要处理数字参数本身,一律用Array.of或字面量;凡是要处理“有长度的东西”,才考虑Array.from。

另一个与之相关的误区是:把Array.from({ length: 3 }, () => 0)和new Array(3).fill(0)等同看待。虽然结果都是[0, 0, 0],但实现路径不同,性能语义也不同。new Array(3)先创建稀疏数组,再逐个填值;Array.from则通过 length 遍历生成密集数组。从可读性看,前者更直白,后者更函数式。两者选哪个更多看团队风格,不必纠结。

6.2 排查空位问题的思路

如果你在代码里发现数组遍历结果不符合预期,第一步就应该检查数组是不是稀疏的,也就是是否存在空位。常见判断方法有两个。

第一种,用in运算符:

const arr = new Array(3); console.log(0 in arr); // false,索引 0 没有属性

第二种,直接看控制台输出。如果是[empty × 3]而不是[undefined × 3],说明是稀疏数组。

如果发现有空位,最快的处理方式是用Array.from()过一遍,把空位变成undefined,然后再继续操作。或者直接用Array.from()作为遍历前的第一步,一步到位。

我早期排查过一个问题:一段渲染逻辑里用arr.forEach给每个格子绘制图形,但是数组是用new Array(n)创建的,没经过fill,结果页面上一片空白。这类问题不报错、不崩溃,最容易让人怀疑是布局和样式的问题,浪费大量时间。排查数组空位应该成为你处理动态数据时的基本反射。

6.3 性能笔记:别在小数组上过度优化

关于性能,先说结论:业务代码里这三个方法的性能差距通常可以忽略不计,除非你在处理超大数组。我用new Array(n).fill()、Array.from({ length: n })跑过简单基准测试,new Array(n).fill()往往更快,因为引擎对数组预分配长度的优化很成熟。而Array.from()因为需要处理映射函数和通用遍历逻辑,开销会略高。但这个差距在十万级以下基本感知不到。

更重要的反而是语义正确性。不要为了节省一次映射的消耗,把本来用Array.from能写清楚的逻辑拆成new Array().map(),代码可读性会明显下降。性能优化要让位于意图清晰,尤其是在数组创建这种高频但规模不一定大的操作上。

还有一点想特别提醒:Array.from()第二参数映射函数里,尽量别写复杂的副作用逻辑。它会按索引顺序执行,如果你在映射过程中又修改了原数组,结果会非常难调试。记得保持映射函数的纯函数风格,用起来会省心很多。

6.4 冷门但有用的细节

  • 数组最大合法长度是2^32 - 2,new Array(4294967296)会抛 RangeError。Array.of(4294967296)则完全没问题,因为它只是把数字当作元素。
  • Array.from()会使用this作为构造函数。如果this不是一个可调用对象,它回退到普通数组。这个机制在自定义数组子类里很重要,写通用库时要留意。
  • Array.of()也一样,受this影响。但它不接受类数组对象,如果你需要对类数组对象做子类转换,用Array.from()更合适。
  • 如果你在浏览器里拿到的是HTMLCollection而不是NodeList,Array.from()同样能处理,因为它们都有length属性和索引访问能力。

在我自己的项目实践中,最常用的组合是:用Array.from({ length: n }, 映射函数)生成索引数组,用Array.of()在可变参数函数里收纳参数,用new Array(n).fill(value)做定长初始化。这三个场景几乎覆盖了日常开发里 90% 的数组创建需求。搞清楚它们背后的规则之后,你写代码时会多一层确定性,排查问题时也不会再被“看起来一样的数组”骗到。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 3:58:06

BootCamp6.1.7577.zip 驱动安装全攻略(Mac/Win11)

简介&#xff1a;2019款13英寸MacBook Pro&#xff08;2端口带触控条&#xff09;用户若要在苹果硬件上安装Windows 10&#xff0c;BootCamp 6.1.7577.zip正是对应的官方驱动与安装引导工具包&#xff0c;可解决触控板多指手势失效、图形性能不足、音频无声、无线与蓝牙连接异常…

作者头像 李华
网站建设 2026/10/2 3:57:04

实体关系抽取Pipeline实践:BiLSTM+CRF与BERT构建知识图谱

简介&#xff1a;面向自然语言处理研究者与知识图谱构建开发者&#xff0c;这份资源提供了一套基于BiLSTMCRF与BERT的实体关系抽取完整解决方案。系统采用分阶段处理架构&#xff0c;先通过双向长短期记忆网络与条件随机场结合的序列标注模型完成实体识别&#xff0c;再借助BER…

作者头像 李华
网站建设 2026/10/2 3:56:38

SpringBoot+Vue毕设实战:大学社团管理系统完整开发指南

这两年帮周围不少学弟学妹看过毕业设计&#xff0c;十个里头少说有六七个都想做“管理系统”。一开始大家交上来的选题五花八门&#xff0c;有图书管理、实验室预约、宿舍报修什么的&#xff0c;但最后能真正落地、答辩不被问倒的&#xff0c;往往是那种场景清晰、角色分工明确…

作者头像 李华
网站建设 2026/10/2 3:56:21

Qt QPushButton 样式设置全指南:QSS状态机与不生效排查

不少做 Qt 桌面端的朋友&#xff0c;第一次碰到“按钮太丑想换换样子”这件事&#xff0c;基本都会经历这么几个阶段&#xff1a;先试着 setFlat(true) 发现只是变扁了&#xff0c;然后去翻文档找到 setStyleSheet&#xff0c;抄一段网上流传的蓝色圆角按钮代码&#xff0c;跑起…

作者头像 李华
网站建设 2026/10/2 3:55:32

SpringBoot+Vue小区管理系统设计与实现全解析

做这个基于SpringBootVue的综合小区管理系统&#xff0c;前后花了差不多两个月。选题阶段目标就很明确&#xff1a;这类系统要覆盖业主信息、物业费、报修、车位这些高频业务&#xff0c;后端用SpringBoot整合MyBatis操作MySQL&#xff0c;前端用Vue做界面&#xff0c;既能把全…

作者头像 李华
网站建设 2026/10/2 3:55:15

多人多AI协同架构设计:代理总线实现AI代理代为交互

开头。AI代理这个词最近火到什么程度&#xff0c;我就不重复了。但真正扎到实际项目里以后&#xff0c;我发现一个很尴尬的现实&#xff1a;大家都在研究单个agent怎么更聪明、工具调用怎么更稳&#xff0c;却很少有人认真想过一个更麻烦的问题——当一群人带着各自的AI代理、在…

作者头像 李华