JS创建数组的两种方式:字面量[]与new Array()的区别

作者:代码拾荒

时间:2026-11-05

浏览:440

深入对比JavaScript中字面量[]与new Array()创建数组的本质区别,详细解析单参数重载歧义、稀疏数组空槽(Empty Slots)遍历陷阱、V8引擎底层解析流程以及日常业务中的最佳选型实践。

文章目录
JS创建数组的两种方式:字面量[]与new Array()的区别
图示:JavaScript 字面量与构造函数创建数组的全貌特征
在日常 JavaScript 开发中,假设你需要初始化一个包含单个元素 5 的数组。如果随手写成 const list = new Array(5),控制台输出的却不是预想的 [5],而是一个长度为 5 且没有任何具体值的空槽数组 [ <5 empty items> ];而如果写成 const list = [5],得到的才是包含单个数值 5 且长度为 1 的预期数组。 这种参数意义的不一致,是 JavaScript 数组字面量 [] 与构造函数 new Array() 最具迷惑性的差异。两者的区别远不仅是写法简繁,还涉及到参数重载的多义性、稀疏数组与密集数组的内存结构、遍历方法的响应逻辑以及 JS 引擎底层的解析性能。

先给出最核心的工程结论:在日常业务开发中,应始终优先使用字面量 [] 创建数组。构造函数 new Array() 仅在需要配合 fill() 批量初始化特定长度占位数组的极少数场景下具备实用价值。下文将围绕参数机制、空槽陷阱、引擎底层与场景选型展开系统剖析。

一、参数解析机制的根本差异(重载歧义陷阱)

字面量语法 [] 与构造函数 new Array() 最直观的差异在于对传入参数的解析机制。字面量语法的行为是单一且严格确定的,而 new Array() 内部实现了一套多参数重载逻辑。

1. 单参数传入时的行为突变

当仅向数组传递一个参数时,两种方式的执行逻辑完全不同:

  • 字面量 [x]:无论 x 是数字、字符串、布尔值还是对象,该项永远被作为数组的第 0 个元素,生成的数组长度必定为 1。
  • 构造函数 new Array(x):引擎会首先检查 x 的类型:
    • 如果 x 是合法的非负整数数值,它不会被当作数组元素,而是被当作数组的长度属性 length,引擎将分配 x 个空槽(Empty Slots);
    • 如果 x 是非数字(如 "5"、true、{}),它才会被作为数组的第一个元素;
    • 如果 x 是负数或存在小数部分的浮点数(如 -1、3.14),JS 引擎将直接抛出致命错误 RangeError: Invalid array length。
// 字面量模式:行为始终保持一致
const litNum = [5];        // 结果: [5],length 为 1
const litStr = ["5"];      // 结果: ["5"],length 为 1

// 构造函数模式:参数类型引发行为分流
const ctorNum = new Array(5);   // 结果: [ <5 empty items> ],length 为 5
const ctorStr = new Array("5"); // 结果: ["5"],length 为 1

// 构造函数接收非法数字直接抛出异常
new Array(-1);   // Uncaught RangeError: Invalid array length
new Array(3.14); // Uncaught RangeError: Invalid array length
JS创建数组的两种方式:字面量[]与new Array()的区别
图 1:输入参数对字面量与构造函数的内部行为分流对比

2. 动态参数引发的潜在 Bug

这种重载机制在处理动态变量时尤为脆弱。假设编写一个通用工厂函数,根据传入的参数列表动态构建数组:

function createList(...items) {
  // 如果错误地使用 new Array 处理展开参数:
  return new Array(...items);
}

createList(1, 2, 3); // 传入3个参数,返回 [1, 2, 3],符合预期
createList(5);       // 仅传入1个数字参数,返回 [ <5 empty items> ],严重偏离预期!
createList("5");     // 传入1个字符串,返回 ["5"]

同一个函数在参数数量从多个变为单个时,因为 new Array() 的参数多义性,返回值的类型与结构产生了断崖式突变。如果采用字面量语法 [...items],则不论 items 的长度为几,语义永远绝对恒定。

二、内存形态与空槽(Empty Slots)对遍历的破坏

当执行 new Array(5) 时,产生的数组在控制台中通常显示为 [ <5 empty items> ]。很多初学者误以为这里的空槽等同于 undefined,但从 ECMAScript 规范和底层的内存结构来看,两者存在本质区别。

1. 空槽并非 undefined,而是未分配属性键

在 JavaScript 中,数组本质上是一个以整数为键的特殊对象。在正常密集数组中,索引是其实际拥有的自有属性(Own Property)。而在 new Array(n) 创建的稀疏数组中,引擎仅仅在内部标记了对象的 length 属性,并没有在内存中为索引 0 到 n - 1 分配属性槽位。

const sparse = new Array(3);
const dense = [undefined, undefined, undefined];

// 检查索引键是否存在于对象自身中
0 in sparse; // false (键 0 根本不存在)
0 in dense;  // true  (键 0 真实存在,值为 undefined)

sparse.hasOwnProperty(0); // false
dense.hasOwnProperty(0);  // true

Object.keys(sparse); // [] (没有可枚举的自身属性键)
Object.keys(dense);  // ['0', '1', '2']
JS创建数组的两种方式:字面量[]与new Array()的区别
图 2:空槽(Empty Slots)在内存形态与各遍历 API 中的处理差异

2. 原型方法对待空槽的两极分化

正是由于空槽缺少实际的属性键,JavaScript 的各类内置遍历方法在处理稀疏数组时产生了明显的规则撕裂:

  • 跳过空槽的传统方法(ES5 系列): forEach()、map()、filter()、every()、some() 在执行时,会先通过内部规范方法检查当前索引是否属于对象的自有属性。如果属于空槽,回调函数将直接跳过,根本不会被调用。
    经典故障示范:
    许多开发者试图用 new Array(3).map(() => 0) 快速生成一个 [0, 0, 0] 的数组,但实际运行结果依然是 [ <3 empty items> ]!因为 map() 发现索引 0、1、2 全是空槽,回调函数被执行了 0 次。
  • 识别空槽的现代方法(ES6+ 迭代协议系列): for...of 循环、展开运算符 [...arr]、Array.from() 以及 find() 基于统一的迭代器协议(Iterator Protocol)运行。迭代器是按数字步长从 0 递增到 length - 1 读取属性的,当读取不存在的键时自动得到 undefined,因此这些方法会将空槽视作 undefined 正常处理。

三、JS 引擎底层解析与执行性能推导

除了代码语义和语法层面的不同,字面量与构造函数在 V8 等现代 JavaScript 引擎的编译与执行管道中也走着截然不同的路径。

JS创建数组的两种方式:字面量[]与new Array()的区别
图 3:V8 引擎解析执行与内存分配的四个关键阶段推导

1. 语法原语与作用域解析开销

字面量 [] 是 JavaScript 语言的核心语法原语(Syntax Primitive)。在词法分析和抽象语法树(AST)构建阶段,解析器将其识别为专用的 ArrayLiteral 节点。到了字节码生成阶段,Ignition 解释器会为其派发专有的 CreateArrayLiteral 指令。这种方式无需在运行时进行任何作用域查找,引擎可直接依据字面量内部的信息克隆预分配模板(Boilerplate)。

相比之下,new Array() 在 AST 中被解析为通用的构造调用节点 CallNew。在运行时执行期间,引擎必须沿当前的词法作用域链向上回溯,查找名为 Array 的全局标识符。尽管在 99.9% 的情况下 Array 指向全局内置构造器,但引擎仍必须执行安全守卫逻辑,防范可能存在的局部变量同名遮蔽(如函数内部定义了 const Array = ...)或原型篡改,这带来了不可消除的额外分支判定与函数调用栈帧开销。

2. ElementsKind 内存形态演化

V8 引擎会根据数组中存储的数据类型维护其内部的“元素形态”(ElementsKind)。

  • 使用字面量 [1, 2, 3] 时,引擎在初始化时即明确知道其中的元素全部为小整数,直接分配 PACKED_SMI_ELEMENTS 连续线性内存空间,后续的数据读取与循环访问均处于最快执行通道。
  • 而使用 new Array(n) 创建定长数组时,由于初始状态全为空槽,引擎分配的初始形态为 HOLEY_ELEMENTS(带孔数组)。一旦数组被打上 HOLEY 标记,哪怕后续为所有槽位都赋值填满,该数组的 ElementsKind 也无法逆向退回到 PACKED 密集形态。后续对该数组的任何索引访问,引擎都必须被迫多增加一步“向原型链追溯属性”的防卫逻辑,导致性能退化。

四、真实开发场景选型与最佳实践

结合上述机制差异,在真实前端与 Node.js 工程中,应当如何规范数组的创建方式?我们可以将需求分为三个具体场景:

JS创建数组的两种方式:字面量[]与new Array()的区别
图 4:数组创建场景选型决策树与工程实践指南

场景 1:已知初始元素或初始化常规容器(95% 的业务场景)

只要在编码时已经确定了初始元素,或者仅仅需要一个空数组供后续业务逻辑进行 push() 操作,唯一合规的方式就是使用字面量:

// 正确做法:简洁、安全、高性能
const list = ["apple", "banana", "orange"];
const users = [];

// 坚决反对的做法:多余的字符,存在隐式查找开销
const badList = new Array("apple", "banana", "orange");
const badEmpty = new Array();

场景 2:需要创建指定长度 N 的序列或填充数组

当业务需要初始化一个长度为 N 的数组时(例如分页占位符、图表坐标刻度),绝对不要裸用 new Array(N),而应根据填充内容选择以下现代推荐方案:

方案 A:统一填充固定默认值(如 0、null 或布尔值)

使用 new Array(n).fill(value)。fill() 方法会显式填充每一个索引槽位,将原本的稀疏空槽转化为实际赋值的密集槽:

// 生成包含 5 个 0 的数组: [0, 0, 0, 0, 0]
const zeros = new Array(5).fill(0);
注意对象引用的浅拷贝问题:如果使用 fill({}),填充的所有位置将指向同一个对象内存地址!若需要每个元素都是独立对象,应使用方案 B。

方案 B:根据索引生成递增数列或独立复杂对象

使用标准方法 Array.from()。这是 ES6 引入的最强大、意图最明确的定长数组创建方法:

// 1. 生成 1 到 5 的递增数字列表: [1, 2, 3, 4, 5]
const range = Array.from({ length: 5 }, (_, index) => index + 1);

// 2. 生成每个槽位都是独立对象的数组
const matrix = Array.from({ length: 3 }, () => ({ status: "pending" }));

方案 C:通过展开运算符合并填充

// 利用展开运算符将空槽自动转化为 undefined
const items = [...Array(5)].map((_, i) => i * 2); // 结果: [0, 2, 4, 6, 8]

场景 3:静态代码检查与团队规范约束

主流的代码规范(如 ESLint 推荐配置、Airbnb JavaScript 风格指南、Google JS Style Guide)均对构造函数创建数组设立了强制规则:

  • ESLint 规则 no-array-constructor:禁止在没有明确长度参数时调用 new Array()。例如 new Array() 和 new Array(1, 2) 会直接触发静态报错,必须改写为 [] 和 [1, 2]。
  • 代码体积与压缩率:在生产构建(Minification)中,[] 仅占用 2 个字节,且无法被更进一步压缩;而 new Array() 即使在变量混淆后也需保留函数调用结构,增加了额外的代码体积。

五、核心差异对照表与避坑速查

最后,我们将两种创建数组方式在日常开发各个维度的核心区别整理成速查表格:

对比维度 字面量 [] 构造函数 new Array()
语法语义 单一直观:中括号内每一项都是元素 多义重载:单数字为长度,其它为元素
单数字输入 [5] → 长度为 1,值为 [5] new Array(5) → 长度为 5,生成 5 个空槽
非法数字输入 [-1]、[3.14] 正常作为元素存储 new Array(-1) 抛出 RangeError
内存结构类型 优先分配 PACKED 密集线性内存 单参数时产生 HOLEY 稀疏数组(带孔)
对 map/forEach 的响应 正常逐项执行回调函数 跳过空槽,回调执行 0 次
引擎执行路径 CreateArrayLiteral 原生快速指令 需要向上查找作用域链与构造调用上下文
工程规范推荐 强制推荐:覆盖 95% 以上业务场景 受限使用:禁止裸用,需配合 fill 等方法
避坑快速检查清单(Checklist):
  1. 永远不要在没有参数或已知元素时使用 new Array(),直接使用 []。
  2. 不要向 new Array() 传递动态变量作为唯一参数,否则当变量为数字时会发生语义突变。
  3. 切记 new Array(n) 产生的空槽不是 undefined,严禁紧接着链式调用 .map() 或 .forEach()。
  4. 需要生成长度为 N 的数列时,首选 Array.from({ length: n }, (_, i) => i)。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章