JavaScript ES6+新增数组方法有哪些?
全面梳理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)。理解这些方法的演进脉络与实际应用边界,能够让数据处理代码更加紧凑且安全。
一、静态构造方法: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]
二、查找与匹配方法:从 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
三、扁平化与变换方法: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]
四、现代索引与批量填充: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。它们在功能上完全对应原有的破坏性方法,但始终返回一个排布更新后的新数组副本,原始数组保持不变。
不可变方法具体代码与用法
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 即可安心使用。
JavaScript怎么准确判断数据类型:typeof、instanceof与Object.prototype.toString
2026-11-05 17:09:21
JS中将数组和对象混合组装为JSON字符串
2026-11-05 16:41:42
JS运算符优先级与结合性的区别是什么?
2026-11-05 13:22:02
JS Map怎么只遍历Key或Value
2026-11-05 13:00:36
D3.js 与 ECharts 选型对比:灵活性 vs 开发效率
2026-11-05 09:45:01
相关标签:






![JS创建二维数组:为什么Array(n).fill([])会导致数据联动](https://img.javascript.cn/tit/articles/13236/20261102/f618c991891ff37efb12df6af2ce0ccb0da8779a907fe1d2f7275d60c84fdd77.webp)

