JavaScript ES6+新增数组方法有哪些?

作者:木棉

时间:2026-11-03

浏览:264

全面梳理JavaScript自ES6(ECMAScript 2015)至最新规范中新增的数组方法,涵盖静态构造、查找匹配、扁平化转换以及ES2023不可变复制方法,附原理图解与实用示例。

文章目录

自 ECMAScript 2015(ES6)起,JavaScript 的 Array 原型和构造函数经历了一系列重大扩展,解决了传统原生数组在元素生成、精准查找、深层扁平化以及数据不可变操作等方面的诸多痛点。现代 JavaScript 新增的数组方法主要可以归纳为五大类:静态构造方法(如 Array.from、Array.of)、精准查找与检索方法(如 find、findIndex、includes、findLast、findLastIndex)、结构变换与扁平化方法(如 flat、flatMap)、索引读取与批量修改方法(如 at、fill、copyWithin),以及 ES2023 推出的不可变复制修改方法(如 toSorted、toReversed、toSpliced、with)。理解这些方法的演进脉络与实际应用边界,能够让数据处理代码更加紧凑且安全。

JavaScript ES6+新增数组方法有哪些?
图 1:JavaScript ES6 至 ES2023+ 核心新增数组方法版本归属与演进概览

一、静态构造方法:Array.from 与 Array.of

在 ES6 之前,创建数组通常依赖字面量 [] 或构造器 new Array()。但面对类数组对象(如 DOM 的 NodeList、函数的 arguments)或集合对象(如 Set、Map)时,开发者必须借助繁琐的 Array.prototype.slice.call(arguments) 借用机制。为此,ES6 提供了两个直观的静态方法。

1. Array.from():将类数组与可迭代对象转为真数组

Array.from(arrayLike[, mapFn[, thisArg]]) 接收一个类数组对象(包含 length 属性及索引结构)或任意可迭代对象(实现了 [Symbol.iterator] 的对象),返回一个全新的真实数组实例。其第二个参数充当即时映射函数,在生成新数组的同时完成数据变换,避免二次遍历。

// 场景 1:将 NodeList 转换为标准数组并提取属性
const elements = document.querySelectorAll('button');
const buttonTexts = Array.from(elements, el => el.textContent.trim());

// 场景 2:快速生成指定长度的递增序列
const range = Array.from({ length: 5 }, (_, index) => index + 1);
// 输出: [1, 2, 3, 4, 5]

// 场景 3:配合 Set 进行数组去重
const rawList = [1, 2, 2, 3, 4, 4, 5];
const uniqueList = Array.from(new Set(rawList));
// 输出: [1, 2, 3, 4, 5]

2. Array.of():解决构造函数的单参数怪异行为

JavaScript 原生的 Array(7) 不会生成元素为 [7] 的数组,而是创建一个长度为 7 的“空位数组”(稀疏数组)。Array.of(...elements) 规范了参数行为,无论传入多少个参数,均始终将其作为新数组的元素返回。

Array(3);        // [ <3 empty items> ], length 为 3
Array.of(3);     // [3], length 为 1
Array.of(1, 2);  // [1, 2]
JavaScript ES6+新增数组方法有哪些?
图 2:Array.from() 解析类数组与迭代器对象的执行步骤与数据处理流程

二、查找与匹配方法:从 find 到 findLast

在早期的 ES5 阶段,要在数组中查找元素主要依赖 indexOf()。然而 indexOf() 存在两大局限:它只能判断简单全等(===),无法传入自定义谓词函数查找对象元素,并且无法识别 NaN。ES6、ES2016 以及 ES2023 对查找能力做了全方位的补全。

1. find() 与 findIndex() (ES6)

这两个方法接收一个测试谓词回调 predicate(element, index, array)。find() 返回第一个使谓词返回真值的元素值,未找到则返回 undefined;findIndex() 则返回对应的索引值,未找到返回 -1。找到匹配项后立即短路终止遍历,性能高于 filter()[0]。

const users = [
  { id: 101, name: 'Alice', active: false },
  { id: 102, name: 'Bob', active: true },
  { id: 103, name: 'Charlie', active: true }
];

// 获取首个满足条件的对象
const activeUser = users.find(u => u.active);
// 输出: { id: 102, name: 'Bob', active: true }

// 获取对应索引
const activeIndex = users.findIndex(u => u.active);
// 输出: 1

2. includes() (ES2016 / ES7)

includes(valueToFind[, fromIndex]) 返回布尔值,用于判定数组是否包含指定元素。它采用 SameValueZero 比较算法,解决了传统 [NaN].indexOf(NaN) === -1 的历史痛点。

const list = [1, 2, NaN];
list.includes(2);   // true
list.includes(NaN); // true (indexOf 返回 -1)

3. findLast() 与 findLastIndex() (ES2023)

在日常业务中,查找“最近的一笔交易记录”或“最后一条有效日志”十分普遍。在 ES2023 之前,开发者往往需要先调用 slice().reverse().find(...),这不仅产生冗余的新数组内存开销,还需额外计算反转后的索引偏移。findLast() 和 findLastIndex() 从数组末尾向前倒序搜索,逻辑清晰且避免内存浪费。

const records = [
  { id: 1, action: 'login', time: '10:00' },
  { id: 2, action: 'view',  time: '10:05' },
  { id: 3, action: 'login', time: '10:30' }
];

// 从后往前查找最后一次登录
const lastLogin = records.findLast(r => r.action === 'login');
// 输出: { id: 3, action: 'login', time: '10:30' }

const lastIndex = records.findLastIndex(r => r.action === 'login');
// 输出: 2
JavaScript ES6+新增数组方法有哪些?
图 3:查找方法在正逆检索方向及特殊值(如 NaN)匹配逻辑上的区别

三、扁平化与变换方法:flat 与 flatMap

在处理树形层级数据、多表联查或分组归档时,多维嵌套数组非常普遍。ES2019 (ES10) 引入了 flat() 与 flatMap() 两个方法,替代了过去使用递归或 reduce + concat 的手动扁平化方案。

1. flat(depth):按指定深度摊平多维数组

flat() 默认只会展开一层(即 depth = 1)。如果需要完全摊平成一维数组,无论嵌套多深,均可传入 Infinity。此外,flat() 在执行时会自动跳过稀疏数组中的空位(empty slots)。

const nested = [1, [2, [3, [4]]]];

nested.flat();          // [1, 2, [3, [4]]] (展开1层)
nested.flat(2);         // [1, 2, 3, [4]]   (展开2层)
nested.flat(Infinity);  // [1, 2, 3, 4]     (完全扁平化)

// 自动剔除稀疏空位
const sparse = [1, , 3, [4, , 6]];
sparse.flat();          // [1, 3, 4, 6]

2. flatMap():映射并扁平化深度为 1 的结果

flatMap(callback) 结合了 map() 与 flat(1) 的功能,在单次遍历中完成“元素转换”与“展平一层”,在性能上比连续链式调用 .map(...).flat() 更加高效。它也非常适合用于在数组中实现条件插入、一对多展开或过滤。

// 场景 1:分词展开(一对多)
const sentences = ["hello world", "web standard"];
const words = sentences.flatMap(s => s.split(" "));
// 输出: ["hello", "world", "web", "standard"]

// 场景 2:同时完成过滤与映射(代替 filter + map 两次循环)
const rawNumbers = [10, -5, 20, -1, 30];
// 过滤掉负数,且正数翻倍:返回空数组将被扁平化丢弃,单个值被展开保留
const processed = rawNumbers.flatMap(n => (n > 0 ? [n * 2] : []));
// 输出: [20, 40, 60]
JavaScript ES6+新增数组方法有哪些?
图 4:flat() 深度展开原理与 flatMap() 单步流水线处理对比

四、现代索引与批量填充:at、fill 与 copyWithin

1. at(index) (ES2022):告别繁琐的负向索引计算

在 ES2022 之前,若想获取数组的倒数第一个元素,通常写成 arr[arr.length - 1] 或 arr.slice(-1)[0]。前者需要重复书写数组变量名,后者则多产生一个单元素切片数组。Array.prototype.at(index) 引入了类似 Python 的负数索引:-1 表示最后一个元素,-2 表示倒数第二个,语法极为精简。

const colors = ['red', 'green', 'blue', 'purple'];

colors.at(0);   // 'red'
colors.at(-1);  // 'purple' (等价于 colors[colors.length - 1])
colors.at(-2);  // 'blue'
colors.at(99);  // undefined (越界安全返回)

2. fill() 与 copyWithin() (ES6):原地数据填充与位移

fill(value[, start[, end]]) 用固定值填充数组的指定区间,通常与 Array(n) 结合进行快速初始化。但需要特别提醒:如果填充值是对象引用,所有位置将共享同一个引用,修改其中一项会影响所有元素。

copyWithin(target, start[, end]) 会在数组内部复制指定序列至目标位置,而不会改变原数组的整体长度,属于偏底层的高性能内存块移动操作。

// 快速初始化固定长度的数字数组
const zeros = new Array(4).fill(0); // [0, 0, 0, 0]

// 注意避坑:引用共享问题
const grid = new Array(3).fill([]); 
grid[0].push('x');
// grid 变为 [['x'], ['x'], ['x']],因为子数组引用相同!
// 正确做法:Array.from({ length: 3 }, () => [])

// copyWithin 示例
const seq = [10, 20, 30, 40, 50];
// 从索引 3 (40) 开始到结束,复制并覆盖到索引 0
seq.copyWithin(0, 3);
// 输出: [40, 50, 30, 40, 50]

五、ES2023 不可变时代:Change Array by Copy

在 React、Vue、Redux 等现代前端框架中,“数据不可变”(Immutable)是状态管理和响应式追踪的核心基石。然而,JavaScript 早期的核心排序和修改方法(如 sort()、reverse()、splice())都是就地破坏性操作(in-place mutation),直接篡改原数组。过去开发者必须先书写 [...arr].sort() 才能维持原数据不变。

ECMAScript 2023 正式引入了 4 个“非破坏性”复制修改方法(Change Array by Copy 提案):toSorted、toReversed、toSpliced 和 with。它们在功能上完全对应原有的破坏性方法,但始终返回一个排布更新后的新数组副本,原始数组保持不变。

JavaScript ES6+新增数组方法有哪些?
图 5:破坏性原地修改与 ES2023 不可变副本方法的运行机制与内存影响

不可变方法具体代码与用法

const source = ['Vue', 'React', 'Angular'];

// 1. toSorted:返回排序后的新数组
const sorted = source.toSorted(); 
// sorted: ['Angular', 'React', 'Vue']
// source: ['Vue', 'React', 'Angular'] (未被修改)

// 2. toReversed:返回倒序排列的新数组
const reversed = source.toReversed(); 
// reversed: ['Angular', 'React', 'Vue']

// 3. toSpliced:纯函数版本的 splice
// 从索引 1 开始删除 1 个元素,并插入 'Svelte'
const spliced = source.toSpliced(1, 1, 'Svelte'); 
// spliced: ['Vue', 'Svelte', 'Angular']

// 4. with:单元素非破坏性替换 (语法糖)
// 将索引 0 处的元素替换为 'Solid'
const replaced = source.with(0, 'Solid'); 
// replaced: ['Solid', 'React', 'Angular']

六、全量方法速查与选型决策指南

为了帮助读者在日常开发中快速选用最合适的方法,下表汇总了 ES6 至 ES2023 新增的核心数组方法、对应规范版本、是否篡改原数组以及典型应用场景:

方法名 引入规范 改变原数组? 返回值 核心场景与建议
Array.from() ES6 (2015) 否 全新数组 将 NodeList、Set、arguments 转化为真实数组,并支持一步映射。
Array.of() ES6 (2015) 否 全新数组 按单参数或多参数规范化创建数组,规避 Array(n) 空位问题。
find() / findIndex() ES6 (2015) 否 元素 / 索引 正向查找首个匹配复杂谓词的对象或项,短路提升效率。
fill() ES6 (2015) 是 当前原数组 快速批量填入初始值,若填充引用对象需注意共享引用陷阱。
copyWithin() ES6 (2015) 是 当前原数组 数组内部浅拷贝覆盖,适合固定长度序列的偏移排布。
includes() ES2016 (ES7) 否 Boolean 判断元素是否存在,支持正确识别 NaN。
flat() ES2019 (ES10) 否 新扁平数组 递归铺平多层嵌套数组,支持 Infinity 彻底扁平化并过滤空位。
flatMap() ES2019 (ES10) 否 新扁平数组 单次遍历完成映射与展开 1 层,兼具高效一对多和过滤转换功能。
at() ES2022 (ES13) 否 指定位置元素 负向索引读取,精简 arr[arr.length - 1] 书写方式。
findLast() / findLastIndex() ES2023 否 元素 / 索引 从后向前查找满足条件的最后一条业务记录,避免临时反转数组。
toSorted() ES2023 否 新排序数组 替代 [...arr].sort(),保证状态不可变,适合前端响应式数据流。
toReversed() ES2023 否 新倒序数组 替代 [...arr].reverse(),不污染源数组。
toSpliced() ES2023 否 新裁剪数组 替代破坏性 splice(),实现切片、删除或插入的纯函数调用。
with() ES2023 否 新替换数组 单点更新数组项的纯函数,免去复杂的浅拷贝展开运算符。
生产环境兼容性建议

ES6 至 ES2022 的数组方法(如 Array.from、includes、flat、at)已被当今各大主流浏览器(Chrome、Firefox、Safari、Edge)与 Node.js 长期完整支持;而 ES2023 的不可变复制方法(toSorted、with 等)在 Chrome 110+、Safari 16+、Node.js 20+ 中已原生就绪。若需兼容较旧的运行环境,引入官方标准 core-js 对应的 Polyfill 即可安心使用。

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章