JS使用Array.from和Array.of创建数组的方法

作者:拾光时间:2026-11-05 10:43:07浏览:500

深入解析JavaScript中Array.of与Array.from创建数组的方法,涵盖传统Array构造函数的参数陷阱、类数组与可迭代对象转换、内置映射管道与实战应用技巧。

JavaScript 核心进阶
语法标准:ECMAScript 6 (ES2015) 主题:数组静态构造与结构转换
在 JavaScript 开发中,使用字面量 [] 是构造数组最常用的直观手段,但在处理动态参数和集合类型转换时,传统方式存在显著短板:new Array() 构造函数存在严重的参数重载歧义,而 DOM NodeList、函数 arguments 等类数组对象又缺乏原生数组方法。针对这两类具体痛点,ES6 正式引入了 Array.of() 与 Array.from() 两个静态方法。前者旨在实现确定性的参数装箱,彻底消除构造歧义;后者则提供了一站式的类数组及可迭代对象转换流水线,并支持单次遍历的原地映射。
JS使用Array.from和Array.of创建数组的方法
图 0:Array.of() 与 Array.from() 的设计定位与数据流向概览

一、Array.of():解决构造函数的参数重载歧义

在 ES6 之前,通过构造函数 new Array() 动态生成数组时,存在一个长期困扰开发者的设计缺陷:函数的行为会根据传入参数的数量和类型发生突变。

1. 传统 Array 构造函数的行为缺陷

当向 Array() 传入多个参数时,它会将所有参数作为新数组的元素;但当只传入一个单一数值参数时,它并不会生成包含该数值的数组,而是将其解释为数组的预设长度(length),并在内存中生成一串稀疏的“空槽”(empty slots / holes)。

// 传统 Array() 构造函数的怪异行为
const arr1 = new Array(3);      // 期望得到 [3],实际输出: [ <3 empty items> ]
const arr2 = new Array(1, 2);   // 输出: [1, 2]
const arr3 = new Array('3');    // 输出: ['3']

// 稀疏空槽无法被正常高阶函数迭代
arr1.forEach(item => console.log(item)); // 完全无输出,循环直接跳过空槽!
const mapped = arr1.map(() => 1);      // 输出: [ <3 empty items> ],回调根本未执行

这种不一致性在编写通用工具函数或高阶组件时极易引发偶发性 Bug。如果传入参数是通过变量动态计算或解构注入的,开发者无法预料传入的单一变量何时会是一个数字,从而导致数组结构被破坏。

2. Array.of() 的统一规范语法

Array.of() 的设计理念非常纯粹:无论传入几个参数、无论参数是何种数据类型,均一律作为生成的数组元素依次排列。

语法定义:Array.of(element0[, element1[, ...[, elementN]]])

// Array.of() 表现出高度的确定性与一致性
const of1 = Array.of(3);        // 输出示例: [3],长度为 1,索引 0 为数字 3
const of2 = Array.of(1, 2, 3);   // 输出示例: [1, 2, 3]
const of3 = Array.of(undefined);// 输出示例: [undefined],非空槽,具备真实索引
const of4 = Array.of();         // 输出示例: [],空参数返回空数组

// 安全进行链式处理
of1.map(x => x * 2);            // 输出示例: [6],正常执行
JS使用Array.from和Array.of创建数组的方法
图 1:new Array() 构造陷阱与 Array.of() 行为一致性对比
💡 关键差异总结
Array.of() 主要用于修复 Array() 构造函数的历史缺陷。在绝大多数需要通过常量字面量定义数组的场景中,直接书写 [1, 2, 3] 仍是首选;而当你在编写工厂函数、包装函数或需要将未知参数列表安全装箱成数组时,应坚决使用 Array.of(...args) 取代 new Array(...)。

二、Array.from():类数组与可迭代对象的转换流水线

与专注参数装箱的 Array.of() 不同,Array.from() 承担的是结构转换与数据映射的双重任务。它是现代 JavaScript 中将各种“类似数组的集合”转换为正统数组的标准枢纽。

1. 转换目标的判定范围

Array.from() 可以处理两大类典型结构:

  • 类数组对象(Array-like Object):拥有非负整数属性 length,且各元素具有数字索引的对象。典型如函数的内置变量 arguments、DOM 的 HTMLCollection,以及开发者手写的形如 { 0: 'a', 1: 'b', length: 2 } 的普通对象。
  • 可迭代对象(Iterable Object):实现了 ES6 迭代器接口 [Symbol.iterator] 的数据结构,包括 Set、Map、原生字符串 String、DOM NodeList 以及 Generator 生成器对象等。

2. 语法签名与第二参数(内置映射函数)

语法定义:Array.from(arrayLike[, mapFn[, thisArg]])

  • arrayLike(必需):待转换的目标对象。
  • mapFn(可选):转换过程中对每个元素执行的回调函数,签名类似于数组的 map 方法:(element, index) => transformedElement。
  • thisArg(可选):执行 mapFn 时的上下文对象。
JS使用Array.from和Array.of创建数组的方法
图 2:Array.from() 执行流程与内建映射流水线
⚡ 性能优势:为什么提倡使用第二参数 mapFn?
许多开发者习惯写 Array.from(source).map(fn),这会在内存中先完整分配一个临时数组,然后再通过 .map() 产生第二个目标数组,造成额外的内存开销与二次垃圾回收。而使用 Array.from(source, fn),引擎在由源对象读取数据向新数组落盘的过程中,直接在同一遍历周期内原地完成了计算与赋值,只分配一次数组内存,对大容量集合处理具有显著的性能提升。

三、Array.from() 实战操作步骤与典型应用场景

为了让读者能够在日常业务中迅速套用,以下整理了四种最具代表性的操作步骤与代码模板。

JS使用Array.from和Array.of创建数组的方法
图 3:常见数据源转换为数组的判定路径与操作指南

场景一:无中生有生成指定长度与步长的数字序列

传统实现“生成 1 到 5 的数字序列”往往需要编写一个空数组并执行 for 循环,代码繁琐。利用包含 length 的普通对象配合 Array.from(),可以一行代码优雅生成:

// 生成连续数字序列 [1, 2, 3, 4, 5]
const range = Array.from({ length: 5 }, (_, index) => index + 1);
console.log(range); 
// 示例输出: [1, 2, 3, 4, 5]

// 生成偶数序列 (步长为 2) [0, 2, 4, 6, 8]
const evens = Array.from({ length: 5 }, (_, index) => index * 2);
console.log(evens);
// 示例输出: [0, 2, 4, 6, 8]

运行原理:{ length: 5 } 满足类数组对象的最低契约。因为没有声明具体索引,Array.from 默认将其 5 个位置视为 undefined,并传递给第二参数中的映射函数。首参使用下划线 _ 占位忽略该 undefined,直接利用自增的索引 index 计算出业务所需的数值。

场景二:DOM 集合的批量提取与功能增强

通过 document.querySelectorAll() 获取的 NodeList 虽然在现代浏览器中支持 forEach,但仍然缺少 filter、map、reduce 和 some 等强力工具方法。

// 业务场景:批量获取所有选中的复选框的真实值,并转为小写
const checkboxes = document.querySelectorAll('input[type="checkbox"]:checked');

// 一步到位完成转换与属性提取
const values = Array.from(checkboxes, el => el.value.toLowerCase());

// 此时 values 是正统数组,可直接调用链式数组方法
const filtered = values.filter(val => val !== '');
console.log(filtered);

场景三:精准拆分包含 Emoji 与多字节的 Unicode 字符串

在 JavaScript 中,部分生僻汉字(如“𠮷”)或现代表情符号(Emoji 如“🚀”、“👨‍👩‍👧”)由 4 个字节(即两个 UTF-16 代理对 / Surrogate Pairs)构成。如果直接调用字符串的 .split('') 方法,会按 2 字节将代理对强行撕裂成乱码。

const text = '𠮷野家🚀';

// 传统 split 方式产生乱码
const splitArr = text.split('');
console.log(splitArr);
// 示例输出: ['\uD842', '\uDFB7', '野', '家', '\uD83D', '\uDE80'] (长度 6,前缀乱码)

// Array.from 遵循 Unicode 码点 (Code Point) 迭代
const codePointArr = Array.from(text);
console.log(codePointArr);
// 示例输出: ['𠮷', '野', '家', '🚀'] (长度 4,字符完整保留,完全准确)

场景四:配合 Set 实现高效去重与类型恢复

const rawNumbers = [10, 20, 10, 30, 20, 40];

// 利用 Set 快速剔除重复项,并无缝转回标准数组
const uniqueNumbers = Array.from(new Set(rawNumbers));
console.log(uniqueNumbers);
// 示例输出: [10, 20, 30, 40]

四、Array.of 与 Array.from 核心差异对比与注意事项

为了帮助开发者在编码时准确选型,下表从入参形式、处理机制和行为边界两个维度对两项技术进行了全面对照:

对比维度 Array.of() Array.from()
核心设计目的 参数一致性装箱,替代易生歧义的 new Array() 类型跨越转换,将类数组/可迭代对象转换为真实数组
入参形态 可变参数列表(...items) 首参必须为一个集合对象(带 length 或可迭代)
参数与元素关系 传入的每一个入参,对应最终数组里的一个成员 传入一个对象,提取拆解其内部数据成为多个成员
内置映射处理 不支持(需后续链式调用) 支持第二参数 mapFn,单次遍历原位处理
空位与稀疏数组 严格按实参生成密集数组 将源对象未定义的空位自动转换为 undefined 密集元素
对比展开运算符 [...] 等价于字面量组装,但适合动态函数式封装 适用面明显大于 [...](展开运算符无法处理非可迭代的纯类数组)
JS使用Array.from和Array.of创建数组的方法
图 4:Array.of 与 Array.from 核心特性对照全景矩阵

开发注意事项与边界防范

  1. 浅拷贝(Shallow Copy)陷阱:Array.from() 产生的新数组是一个全新分配的浅拷贝实例。如果源集合中包含对象或嵌套数组,新数组中保存的仍然是对应内存地址的引用。修改新数组中的嵌套对象,源数据也会同步改变。
  2. length 属性合法性校验:如果向 Array.from() 传递的对象没有实现迭代器协议,且其 length 属性不是有效的正数(例如为负数、NaN 或无法转化为数字的字符串),转换结果将直接退化为一个空数组 [],而不会抛出运行时错误。
  3. Array.from vs 展开运算符(Spread Operator):很多初学者认为 [...source] 可以完全替代 Array.from(source)。实际上,展开运算符依赖底层必须部署了 Symbol.iterator。对于最常见的纯类数组对象 { 0: 'a', length: 1 },使用 [...obj] 会直接抛出 TypeError: obj is not iterable 致命错误,而 Array.from(obj) 则能完美转换为 ['a']。
作者最新文章

相关标签:

相关文章
更多