JS之Array构造函数上的静态方法与实例解读
在 JavaScript 中,Array 既是一个构造函数,也是一个全局对象。它身上的方法分为两类:静态方法(直接通过 Array.xxx 调用)和实例方法(通过数组实例 [].xxx 调用)。
一、 Array 实例方法
1.1 Array.prototype.at()
at() 方法接收一个整数值,并返回该索引对应的元素。
1.1.1 语法与参数
参数: index (整数)。
- 正数: 从左往右找(从 0 开始)。
- 负数: 从右往左找(从 -1 开始)。
返回值: 对应索引的元素。如果找不到(索引超限),则返回 undefined。
const arr = ['Apple', 'Banana', 'Cherry']; console.log(arr.at(0)); // "Apple" console.log(arr.at(-1)); // "Cherry" (倒数第一个) console.log(arr.at(-2)); // "Banana" (倒数第二个) console.log(arr.at(10)); // undefined
1.1.2 注意点
- 不仅仅是数组: 字符串 也能用。“Hello”.at(-1) 会返回 “o”。
- 非破坏性: 它只是读取值,绝对不会像 splice 或 pop 那样修改原数组。
1.1.3 类似实现方法
除了 at(),通常有以下几种替代方案:
| 方案 | 示例 | 优缺点 |
|---|---|---|
| 方括号索引 | arr[arr.length - 1] | 最传统,但取末尾元素很麻烦。 |
| slice() | arr.slice(-1)[0] | 支持负数,但它会创建一个新数组,性能略低,写起来也绕。 |
| pop() | arr.pop() | 能拿到最后一个,但它会改变(删除)原数组。 |
1.2 Array.prototype.concat()
Array.prototype.concat() 是 JavaScript 中最经典、最常用的合并方法。
它的核心作用是将 两个或多个数组(或值)连接成一个全新的数组。
1.2.1 语法与参数
const newArray = oldArray.concat(value1, value2, ..., valueN);
- 参数: 可以是数组,也可以是具体的值(字符串、数字、对象等)。
- 返回值: 一个全新的数组实例,包含调用它的数组元素,以及按顺序添加的所有参数元素。
- 非破坏性: 它不会修改原数组(oldArray 保持不变)。
1.2.2 使用场景
A. 合并多个数组
最直观的用途,把碎片化的数据拼成大列表。
const digital = [1, 2]; const analog = [3, 4]; const alpha = ['a', 'b']; const combined = digital.concat(analog, alpha); // [1, 2, 3, 4, 'a', 'b']
B. 混合合并(值与数组)
你可以在合并数组的同时,顺手塞进几个散装的值。
const base = [1, 2]; const result = base.concat(3, [4, 5]); // [1, 2, 3, 4, 5]
C. 浅拷贝数组(传统做法)
const copy = arr.concat();
1.2.3 注意点
① 浅拷贝警告 (Shallow Copy)
concat 进行的是 浅拷贝。如果数组里存的是对象,合并后的新数组和原数组指向的是同一个对象地址。修改其中一个,另一个也会跟着变。
② 只能扁平化一层数组
concat 会自动拆解参数中的第一层数组,但不会递归拆解。
const arr = [1].concat([2, [3, 4]]); // 结果是 [1, 2, [3, 4]],而不是 [1, 2, 3, 4]
如果需要完全拍平,请配合 flat() 方法使用。
1.2.4 类似实现方法
目前开发中,concat 最强力的竞争对手是 ES6 的 扩展运算符 (…)。
| 方法 | 示例 | 优点 | 缺点 |
|---|---|---|---|
| concat() | a.concat(b) | 语义明确;兼容性极好(ES3)。 | 语法略显老旧。 |
| 扩展运算符 | “[…a, …b]” | 现代主流;更简洁;易于在中间插入值。 | 需要编译(Babel)以支持旧环境。 |
| push(…others) | a.push(…b) | 性能极高(对于超大数组)。 | 会改变原数组 a。 |
1.3 Array.prototype.constructor
在 JavaScript 中,每个对象都有一个 constructor 属性,指向创建该实例对象的构造函数。
- 参数: 无(它是一个属性,不是一个需要传入参数执行的方法,尽管你可以像函数一样调用它)。
- 返回值: 返回对创建了该数组实例的 Array 构造函数的引用。
- 本质: [].constructor === Array 返回 true。
1.3.1 为什么要引入这个属性?(核心意义)
在面向对象编程(OOP)中,constructor 的存在是为了让实例能够溯源。
- 识别身份: 知道这个对象是从哪个“模具”里出来的。
- 动态创建: 如果你手里有一个实例 a,但你不知道它的具体类型,你可以通过 a.constructor 来创建一个和它同类型的新实例,而不需要硬编码类名。
1.3.2 使用场景
A. 准确判断类型
虽然现在流行用 Array.isArray(),但在处理自定义类时,constructor 非常有用。
const arr = [1, 2, 3];
if (arr.constructor === Array) {
console.log("这是一个数组");
}
B. 基于现有实例创建新实例
当你编写一个通用的函数,需要返回一个与输入对象类型相同的新对象时:
function cloneEmpty(obj) {
// 无论传进来的是 Array 还是自定义的 MyArray,都能创建正确的类型
return new obj.constructor();
}
const newArr = cloneEmpty([1, 2, 3]); // 返回 []
1.4 Array.prototype.copyWithin() - 破坏性
它的行为像是在数组内部进行了一次 “复制 + 粘贴”。
1.4.1 语法与参数
arr.copyWithin(target, start, end); // 左闭右开
- target (必填):复制到的目标索引位置。如果是负数,则从末尾开始计算。
- start (可选):开始复制的源元素索引(默认为 0)。
- end (可选):结束复制的源元素索引(不包含该索引,默认为 arr.length)。
- 返回值:返回修改后的数组(不产生新数组)。
const arr = [1, 2, 3, 4, 5]; // 把索引 3 到 4 的元素(也就是数字 4),复制到索引 0 的位置 const result = arr.copyWithin(0, 3, 4); console.log(arr); // 输出: [4, 2, 3, 4, 5] console.log(result); // 输出: [4, 2, 3, 4, 5] console.log(arr === result); // 输出: true (指向同一个内存地址)
1.4.2 行为特征(重点)
- 修改原数组:它直接在原数组上操作,不产生新数组。
- 长度不变:它不会改变数组的 length。如果复制的内容超出了数组末尾,多余的部分会被截断。
- 高性能:它在内存内部移动数据,速度非常快。
由于 copyWithin 始终维持原数组的 length,它就像是在一个固定长度的容器里挪动东西:如果挪过去的东西太长,放不下的部分就会被“挤出去”丢掉。
const arr = ['a', 'b', 'c', 'd', 'e']; // 目标位置 (target): 3 // 开始读取 (start): 0 // 结束读取 (end): 默认到末尾 (索引 5) // 复制的内容本应是: ['a', 'b', 'c', 'd', 'e'] (长度为 5) arr.copyWithin(3, 0); console.log(arr); // 输出: ["a", "b", "c", "a", "b"] // 数组长度依然是: 5
1.5 Array.prototype.entries()
它主要用于将数组转换为一个可迭代的对象,方便我们在遍历时同时获取“索引”和“值”。
1.5.1 语法与参数
const iterator = arr.entries();
- 参数: 无。
- 返回值: 返回一个新的 Array Iterator(数组迭代器) 对象。
- 内容: 该迭代器每迭代一步,都会返回一个数组 [index, element]
1.5.2 为什么要引入这个方法?
在 entries() 出现之前,JavaScript 遍历数组面临两难选择:
- for 循环: 能拿到索引 i,但语法繁琐。
- forEach(): 能同时拿索引和值,但无法使用 break 或 continue 跳出循环。
- for…of: 语法最简洁,但默认只能拿到值,拿不到索引。
引入意义: entries() 让 for…of 能够以极其优雅的方式同时获取 索引 和 值。
数组本身确实是可迭代的。当你直接使用 for…of 遍历数组时,它默认调用的是数组的 values() 方法。只能拿到值,拿不到索引。

1.5.3 使用场景
A. 在 for…of 中获取索引(最常用)
const fruits = ['Apple', 'Banana', 'Cherry'];
for (const [index, value] of fruits.entries()) {
console.log(`第 ${index} 个水果是 ${value}`);
}
B. 处理稀疏数组
entries() 会遍历数组中的“空洞”(holes),并将其值视为 undefined。这比 forEach 更严谨,因为 forEach 会直接跳过空单元。
const sparse = [1, , 3];
for (const [i, v] of sparse.entries()) {
console.log(i, v); // 输出: 0 1, 1 undefined, 2 3
}
C. 快速将数组转为 Map
const arr = ['a', 'b', 'c'];
const map = new Map(arr.entries());
// Map(3) { 0 => 'a', 1 => 'b', 2 => 'c' }
1.6 Array.prototype.every()
它的核心作用是:检查数组中的 所有 元素是否都符合某个条件。
1.6.1 语法与参数
const allPassed = arr.every(callbackFn(element, index, array), thisArg);
callbackFn (回调函数):对每个元素执行的函数,返回 true 表示通过,false 表示失败。
- element:当前正在处理的元素。
- index:当前元素的索引。
- array:调用 every 的原数组。
thisArg (可选):执行回调时用作 this 的值。
返回值:布尔值 (true 或 false)。
1.6.2 行为特征:短路机制 (Short-circuiting)
every() 非常聪明。它不需要遍历完整个数组:
- 只要发现一个元素不符合条件,它会立即返回 false 并停止遍历。
- 只有当所有元素都返回 true 时,它才会返回 true。
1.6.3 避坑指南
① 空数组陷阱 (重点!)
对于空数组 [],every() 总是返回 true。
这被称为“空真理”(Vacuous truth)。逻辑依据是:在一个空集合中,没有任何元素违反条件。
[].every(x => x > 10); // true!
② 非破坏性:它不会修改原数组。
1.7 Array.prototype.fill() - 破坏性
它能用一个固定值填充数组的全部或部分。
1.7.1 语法与参数
arr.fill(value, start, end);
- value (必填):填充数组的值。
- start (可选):开始填充的起始索引(默认为 0)。
- end (可选):结束填充的索引(不包含该索引,默认为 arr.length)。
- 返回值:返回修改后的原数组。
1.7.2 使用场景
A. 快速初始化数组
这是最常见的用法,配合 new Array() 使用:
const grades = new Array(5).fill(100); // [100, 100, 100, 100, 100]
B. 重置部分数组数据
将数组中索引 1 到 3 的元素清零:
const data = [10, 20, 30, 40, 50]; data.fill(0, 1, 4); // [10, 0, 0, 0, 50]
1.7.3 避坑指南
① 引用类型陷阱
这是 fill() 最容易翻车的地方。 如果填充的值是一个对象或数组,那么数组里的所有元素都会指向同一个内存地址(浅拷贝)。
const arr = new Array(3).fill({ name: 'Gemini' });
arr[0].name = 'AI';
console.log(arr[1].name); // 也是 'AI'!因为它们是同一个对象。
② 改变原数组
fill() 会直接修改调用它的数组,而不是返回副本。
1.8 Array.prototype.filter()
Array.prototype.filter() 是 JavaScript 中最常用的数据筛选工具。它的核心作用是:从原数组中挑出符合条件的元素,并组成一个新数组。
1.8.1 语法与参数
const newArray = arr.filter(callbackFn(element, index, array), thisArg);
callbackFn (回调函数):对每个元素执行的测试函数。
- 返回 true:保留该元素。
- 返回 false:丢弃该元素。
返回值:一个包含所有通过测试的元素的新数组。如果没有元素通过测试,则返回空数组 []。
const products = [
{ name: 'iPhone', price: 800 },
{ name: 'Case', price: 20 }
];
const expensive = products.filter(p => p.price > 100);
// [{ name: 'iPhone', price: 800 }]
1.8.2 避坑指南
- 非破坏性:它永远不会改变原数组,而是返回一个副本。
- 浅拷贝:如果数组包含对象,filter 返回的新数组里存的是相同的引用。修改新数组中的对象,原数组的对象也会变。
- 稀疏数组:它不会为缺少的元素(空洞)执行回调。
1.9 Array.prototype.find()
Array.prototype.find() 是 JavaScript 中用于定位单个元素的高阶方法。它的核心作用是:返回数组中满足条件的第一个元素的值。
1.9.1 语法与参数
const foundElement = arr.find(callbackFn(element, index, array), thisArg);
callbackFn (回调函数):对每个元素执行的测试函数。
- 返回 true:表示找到了,停止查找并返回该元素。
- 返回 false:继续找下一个。
返回值:数组中第一个通过测试的元素值。如果没有任何元素符合条件,则返回 undefined。
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
];
const user = users.find(u => u.id === 2);
// { id: 2, name: 'Bob' }
1.9.2 避坑指南
- 只找一个:它只返回第一个匹配项。如果你需要找所有匹配项,请使用 filter()。
- 找不到返回 undefined:使用返回值前,最好先做个非空判断,避免 Cannot read property of undefined。
- 无法区分“未找到”和“值为 undefined”:如果数组本身包含 undefined 且符合条件,find 也会返回 undefined。
- 非破坏性:不改变原数组。
1.10 Array.prototype.findIndex()
Array.prototype.findIndex() 是 find() 的亲兄弟。区别在于:find() 返回元素本身,而 findIndex() 返回该元素在数组中的位置(索引)。
1.10.1 语法与参数
const index = arr.findIndex(callbackFn(element, index, array), thisArg);
callbackFn (回调函数):测试函数。
- 返回 true:停止搜索,返回当前索引。
- 返回 false:继续找下一个。
返回值:返回第一个符合条件的元素的索引(从 0 开始)。如果没有任何元素符合条件,则返回 -1。
const users = [{id: 1, name: 'A'}, {id: 2, name: 'B'}, {id: 3, name: 'C'}];
const index = users.findIndex(u => u.id === 2);
if (index !== -1) {
users.splice(index, 1); // 成功删除 'B'
}
1.10.2 避坑指南
- 找不到返回 -1:这是和 find() 最明显的区别(find 找不到返回 undefined)。在做后续操作(如 splice)前,一定要判断是否等于 -1,否则 splice(-1, 1) 会删掉数组的最后一个元素!
- 短路特性:一旦找到符合条件的,后面剩下的元素就不会再遍历了。
- 非破坏性:它只负责“看”,不负责“改”。
1.10.2 类似实现方法
| 方法 | 返回值 | 适用场景 |
|---|---|---|
| indexOf() | 索引 | 仅限于查找简单的原始类型(如数字、字符串)。 |
| find() | 元素本身 | 当你只需要获取数据,而不需要它的索引位置时。 |
| lastIndexOf() | 索引 | 从后往前找第一个符合条件的索引(仅限简单值)。 |
| findLastIndex() | 索引 | ES2023 引入,支持从后往前通过回调函数找索引。 |
1.11 Array.prototype.findLast()
它的功能与 find() 完全一样,唯一的区别在于它的查找方向:它从数组的最后一个元素开始反向遍历。
1.11.1 语法与参数
const found = arr.findLast(callbackFn(element, index, array), thisArg);
- 参数:与 find() 一致,接收一个回调函数(元素、索引、数组实例)和一个可选的 this 指向。
- 返回值:返回倒数第一个符合条件的元素。如果没有任何元素符合条件,则返回 undefined。
const timeline = [
{ id: 1, action: 'login', time: '10:00' },
{ id: 2, action: 'post', time: '10:05' },
{ id: 3, action: 'login', time: '10:10' }
];
// 查找最后一次登录的信息
const lastLogin = timeline.findLast(item => item.action === 'login');
// { id: 3, action: 'login', time: '10:10' }
1.12 Array.prototype.findLastIndex()
它是 findIndex() 的反向版本:从数组末尾开始向前搜索,返回第一个符合条件的元素的索引。
1.12.1 语法与参数
const index = arr.findLastIndex(callbackFn(element, index, array), thisArg);
参数:接收一个回调函数(当前元素、当前索引、数组本身)以及一个可选的 this 指向。
返回值:
- 返回满足条件的最后一个(从后往前数第一个)元素的正向索引。
- 如果没有任何元素符合条件,则返回 -1。
const logs = [
{ type: 'save', id: 101 },
{ type: 'edit', id: 102 },
{ type: 'save', id: 103 },
{ type: 'edit', id: 104 }
];
// 找到最后一次 'save' 类型操作的索引
const lastSaveIndex = logs.findLastIndex(item => item.type === 'save');
console.log(lastSaveIndex); // 2
1.12.2 避坑指南
- 索引仍然是正向的:即使是反向遍历,返回的索引值依然是基于数组开头的(0, 1, 2…)。
- 非破坏性:它不会修改原数组。
1.13 Array.prototype.flat()
它的核心作用是:按照指定的深度递归遍历数组,将子数组中的元素合并到新数组中,俗称 “数组拍平”。
1.13.1 语法与参数
const newArray = arr.flat(depth);
depth (可选):指定要提取嵌套数组的结构深度。
- 默认为 1。
- 如果传入 Infinity,则无论嵌套多少层都会被拍平。
返回值:一个包含所有子数组元素组成的新数组。
const nested = [1, [2, [3, 4]]]; console.log(nested.flat()); // [1, 2, [3, 4]] (默认深度为1) console.log(nested.flat(2)); // [1, 2, 3, 4]
彻底拍平(不管嵌套多深)当你处理来源不明、嵌套层级混乱的数据时:
const crazy = [1, [2, [3, [4, [5]]]]]; const flatResult = crazy.flat(Infinity); // [1, 2, 3, 4, 5]
1.13.2 避坑指南
- 非破坏性:它返回一个新数组,原数组保持不变。
- 浅拷贝:拍平过程中,如果元素是对象,复制的依然是引用地址。
1.14 Array.prototype.flatMap()
它就是 map() 和 flat(1) 的组合体,但它比手动链式调用这两者更高效。
1.14.1 语法与参数
const newArray = arr.flatMap(callbackFn(element, index, array), thisArg);
- callbackFn (回调函数):与 map 相同,但在映射后,它会对返回的结果自动进行 一层(深度为 1) 的拍平。
- 返回值:一个新的数组,其中每个元素都是回调函数结果被拍平后的产物。
const orders = [
{ id: 1, items: ["苹果", "香蕉"] },
{ id: 2, items: ["橙子"] },
{ id: 3, items: ["西瓜", "葡萄"] }
];
const res = orders.flatMap(order => order.items);
console.log(res);
// 结果是:["苹果", "香蕉", "橙子", "西瓜", "葡萄"]
// 直接拿到了干净的商品清单!
1.14.2 避坑指南
- 只能拍平一层:这是 flatMap 与 flat(depth) 的最大区别。如果你的回调函数返回的是一个三层嵌套数组,它只会把它变成两层。
- 效率优于链式调用:arr.flatMap(f) 比 arr.map(f).flat() 快,因为它不需要创建中间的临时数组。
- 非破坏性:它不修改原数组。
1.15 Array.prototype.forEach()
Array.prototype.forEach()是JavaScript 中最基础、最常用的遍历方法。它的核心作用是:对数组的每个元素执行一次给定的函数。
1.15.1 语法与参数
arr.forEach(callbackFn(element, index, array), thisArg);
callbackFn(回调函数):为每个元素执行的函数。
- element:当前遍历到的元素。
- index:当前元素的索引。
- array:正在处理的原数组。
- thisArg(可选):执行回调时用作this的值。
返回值:undefined。它不返回任何有意义的结果,本质上是一个“过程控制”工具。
const items = ['apple', 'banana'];
const copy = [];
items.forEach(item => {
copy.push(item.toUpperCase()); // 操作外部变量
});
1.15.2 避坑指南
① 无法中断(不可break/continue)
这是它最大的痛点。 forEach一旦开始,除非抛出异常,否则它会遍历完数组中的所有元素。
- 如果你需要中断循环:请使用传统的for循环、for…of、或者some()/ every()。
② 异步陷阱
forEach不会等待异步函数(如Promise)完成。它会像机关枪一样瞬间打完所有回调,而不理会内部的await。
- 解决方法:使用for…of配合await。
// 错误示例
arr.forEach(async (item) => {
await someAsyncCall(item); // 这里不会按顺序等待
});
1.16 Array.prototype.includes()
它的核心作用是:判断一个数组是否包含一个指定的值,根据情况返回true或false。
1.16.1 语法与参数
const hasValue = arr.includes(searchElement, fromIndex);
searchElement(必填):需要查找的元素值。
fromIndex(可选):从哪个索引处开始查找。
- 如果是负数,则从array.length + fromIndex的位置开始。
- 默认为0。
返回值:布尔值( true/ false)。
const fruits = ['apple', 'banana', 'mango'];
// 1. 基本用法
console.log(fruits.includes('banana')); // true
console.log(fruits.includes('grape')); // false
// 2. 第二个参数:起始查找位置
console.log(fruits.includes('apple', 1)); // false (从索引1开始找,找不到apple)
// 3. 完美处理 NaN
const arr = [1, 2, NaN];
console.log(arr.indexOf(NaN)); // -1 (旧方法的坑)
console.log(arr.includes(NaN)); // true (ES7 正确识别)
1.16.2 避坑指南
- 无法查找“深层”对象:由于它是通过值/引用地址来对比的,所以它无法直接查找对象内容。
- 提示:查找对象是否存在,请使用some()或find()。
- 大小写敏感:对于字符串数组,includes()是严格区分大小写的。
1.17 Array.prototype.indexOf()
Array.prototype.indexOf()是JavaScript 中最经典的索引查找方法。它的核心作用是:从数组中寻找指定元素,并返回它第一次出现的位置索引。
1.17.1 语法与参数
const index = arr.indexOf(searchElement, fromIndex);
- searchElement(必填):要查找的元素。
- fromIndex(可选):开始查找的位置。
如果是负数,则表示从倒数第几个开始找(但查找方向依然是从左往右)。
返回值:
- 找到则返回该元素的第一个索引(数字)。
- 找不到则返回-1。
const beasts = ['ant', 'bison', 'camel', 'duck', 'bison'];
console.log(beasts.indexOf('bison')); // 1
console.log(beasts.indexOf('bison', 2)); // 4 (从索引2开始找)
if (arr.indexOf('something') !== -1) {
// 元素存在
}
const unique = arr.filter((item, index) => arr.indexOf(item) === index); // 只有当元素的“第一次出现索引”等于“当前遍历索引”时,才保留它
1.17.2 避坑指南
严格相等与NaN:indexOf使用的是严格相等( ===)。这导致它有两个局限:
- 无法识别NaN:[NaN].indexOf(NaN)会返回-1。
- 对象查找:只有引用地址完全一致才能找到。
只能找“第一个”:如果数组中有重复元素,它只会返回最左边的那一个。如果你想找最后一个,请使用lastIndexOf()。
1.18 Array.prototype.join()
用于将数组转换为字符串的核心方法。它的作用是将数组的所有元素连接起来,并在元素之间插入指定的连接符。
1.18.1 语法与参数
const str = arr.join(separator);
separator(可选):指定连接每个元素的字符串。
如果省略,默认使用逗号( ,)。
如果传入空字符串( “”),元素之间将没有任何字符。
返回值:一个包含所有数组元素连接而成的新字符串。如果数组长度为0,则返回空字符串。
const elements = ['Fire', 'Air', 'Water'];
console.log(elements.join()); // "Fire,Air,Water"
console.log(elements.join(' - ')); // "Fire - Air - Water"
1.18.2 避坑指南
① 自动类型转换
如果数组中的元素不是字符串,join()会隐式调用它们的toString()方法。
null和undefined:会被转换成空字符串( “”) 。
[1, null, undefined, 4].join('-'); // "1---4"
1.19 Array.prototype.keys()
Array.prototype.keys()是ES6 引入的方法,它与entries()和values()属于同一个迭代器家族。它的核心作用是:返回一个包含数组中所有 索引(键) 的可迭代对象。
1.19.1 语法与参数
const iterator = arr.keys();
参数:无。
返回值:返回一个新的Array Iterator对象,该对象按顺序包含数组的每一个索引值。
const arr = ['a', 'b', 'c'];
for (const key of arr.keys()) {
console.log(key); // 输出: 0, 1, 2
}
const range = [...Array(5).keys()]; // [0, 1, 2, 3, 4]
1.19.2 避坑指南
它是迭代器:就像entries()一样,你不能直接用arr.keys()[0]访问。你需要通过for…of遍历,或者用[…iter]、Array.from(iter)转换成真正的数组。
非破坏性:不会改变原数组。
1.20 Array.prototype.lastIndexOf()
是indexOf()的“倒序版本”。它的核心作用是:从数组的末尾开始向前搜索,返回指定元素最后一次出现的位置索引。
1.20.1 语法与参数
const index = arr.lastIndexOf(searchElement, fromIndex);
searchElement(必填):要查找的元素。
fromIndex(可选):开始反向查找的位置。
默认为arr.length - 1(即从最后一个元素开始找)。
如果是负数,则表示从倒数第几个位置开始向前找。
返回值:
找到则返回该元素的最后一个索引。
找不到则返回-1。
const numbers = [2, 5, 9, 2]; console.log(numbers.lastIndexOf(2)); // 3 console.log(numbers.lastIndexOf(7)); // -1 console.log(numbers.lastIndexOf(2, 2)); // 0 (从索引2开始往前找)
通过对比indexOf和lastIndexOf的结果,可以快速判断一个元素是否在数组中出现了多次:
const isDuplicate = (arr, val) => arr.indexOf(val) !== arr.lastIndexOf(val); isDuplicate([1, 2, 3, 2], 2); // true
1.20.2 避坑指南
① 查找方向
虽然fromIndex可以指定起点,但查找的方向永远是向左(递减)。
举例:[2, 5, 9].lastIndexOf(9, 1)会返回-1,因为从索引1 (数字5) 开始往回找,根本碰不到索引2 上的数字9。
② 严格相等(===)
与indexOf一样,它使用严格相等:
无法识别NaN。
无法通过属性查找对象(除非引用地址完全一致)。
1.21 Array.prototype.map()
它的核心作用是:创建一个新数组,其结果是原数组中的每个元素都调用一次提供的函数后的返回值。
1.21.1 语法与参数
const newArray = arr.map(callbackFn(element, index, array), thisArg);
callbackFn(回调函数) :生成新数组元素的函数。
element:当前正在处理的元素。
index:当前元素的索引。
返回值:一个由每次callbackFn返回的结果组成的新数组。
const users = [
{ id: 1, name: 'Alice', email: 'a@test.com' },
{ id: 2, name: 'Bob', email: 'b@test.com' }
];
const names = users.map(user => user.name);
// ['Alice', 'Bob']
const prices = [10, 20, 30]; const discounted = prices.map(p => p * 0.8); // [8, 16, 24]
1.21.2 避坑指南
- 必须有return:如果你的回调函数里忘记写return,新数组里的对应位置就会是undefined。
1.22 Array.prototype.pop() - 破坏性
它的核心作用是:从数组中删除最后一个元素,并返回该元素的值。
1.22.1 语法与参数
const lastElement = arr.pop();
参数:无。
返回值:
返回从数组中删除的那个元素。
如果数组是空的,则返回undefined。
副作用:此方法会改变原数组(修改数组的length属性)。
const history = ['home', 'settings', 'profile']; const lastPage = history.pop(); console.log(lastPage); // 'profile' console.log(history); // ['home', 'settings']
1.22.2 避坑指南
- 破坏性方法:它会直接修改你的原始数据。如果你需要保留原数组,请先使用[…arr]进行浅拷贝。
- 空数组调用:在空数组上调用不会报错,但会返回undefined。如果你的数组里本就存有undefined,需要小心区分是“取出的值”还是“数组已空”。
- 性能优势:在大型数组中,pop()的执行速度极快( O ( 1 ) O(1) O(1)),因为不涉及元素位移。
1.23 Array.prototype.push() - 破坏性
它的核心作用是:将一个或多个元素添加到数组的末尾。
1.23.1 语法与参数
const newLength = arr.push(element1, element2, ..., elementN);
参数:想要添加到数组末尾的元素(可以是一个,也可以是多个)。
返回值:添加新元素后数组的最新长度 (length)。
副作用:此方法会改变原数组(In-place mutation)。
const list1 = ['a', 'b']; const list2 = ['c', 'd']; list1.push(...list2); // list1 变为 ['a', 'b', 'c', 'd']
1.23.2 避坑指南
- 小心返回值:新手最常犯的错误是以为 push 返回的是修改后的数组。
- const result = arr.push(1); 得到的 result 是 1 (长度),而不是 [… , 1]。
- 破坏性:它会直接修改你的原始数据。在 React 或 Redux 等状态管理框架中,通常应避免使用 push,而改用展开运算符:setItems([…items, newItem])。
1.24 Array.prototype.reduce()
的核心作用是:将数组中的所有元素通过一个累加器(accumulator)函数,最终计算为一个单一的值。
1.24.1 语法与参数
const result = arr.reduce(callbackFn(accumulator, currentValue, currentIndex, array), initialValue);
callbackFn (累加器函数):
accumulator (累计器):上一次回调运行的结果。
currentValue (当前值):数组中正在处理的元素。
initialValue (初始值 - 建议必填):
第一次调用回调函数时 accumulator 的值。
注意:如果不提供,则 accumulator 默认为数组的第一个元素,且跳过第一次循环。
const nums = [1, 2, 3, 4]; const sum = nums.reduce((acc, curr) => acc + curr, 0); // 结果: 10
将数组转换为对象 (数据聚合)
这是 reduce 的杀手锏,比如统计单词出现次数:
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const count = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {});
// 结果: { apple: 3, banana: 2, orange: 1 }
数组去重
虽然 Set 更快,但 reduce 展现了逻辑的灵活性:
const arr = [1, 2, 1, 3, 2, 4];
const unique = arr.reduce((acc, curr) => {
if (!acc.includes(curr)) acc.push(curr);
return acc;
}, []);
1.24.2 避坑指南
- 一定要 return:回调函数如果没有 return,下一次循环的 accumulator 就会变成 undefined。
1.25 Array.prototype.reduceRight()
它的核心逻辑与 reduce() 完全一致,唯一的区别在于遍历方向:它从数组的 最后一个元素开始,向前(向左) 执行累加操作。
1.25.1 语法与参数
const result = arr.reduceRight(callbackFn(accumulator, currentValue, currentIndex, array), initialValue);
遍历顺序:从 index = arr.length - 1 开始,到 index = 0 结束。
参数:与 reduce() 完全相同(累加器、当前值、索引、原数组,以及可选的初始值)。
返回值:通过累加器函数计算得到的单一值。
const words = ["World", "Hello"]; const sentence = words.reduceRight((acc, curr) => acc + " " + curr); // 结果: "Hello World" (从右侧 "Hello" 开始,加上 "World")
1.26 Array.prototype.reverse() - 破坏性
Array.prototype.reverse() 是一个非常直观的方法,用于将数组中元素的顺序颠倒。它是处理排序逻辑和视图转换时的常用工具。
1.26.1 语法与参数
const reversedArray = arr.reverse();
参数:无。
返回值:指向原数组的引用。
副作用:破坏性方法。它会直接修改原始数组,而不是创建一个副本。
切换排序顺序
配合 sort() 使用,可以实现降序排列(虽然 sort 自身也能实现,但 reverse 更语义化):
const numbers = [1, 2, 3, 4, 5]; numbers.reverse(); // [5, 4, 3, 2, 1]
1.26.2 避坑指南
① 原地修改 (In-place)
- 这是 reverse 最容易被忽视的特性。它会改变原数组:
const a = [1, 2, 3]; const b = a.reverse(); console.log(a); // [3, 2, 1] —— 原数组被改了! console.log(b === a); // true —— 它们指向同一个内存地址
如果你需要保留原数组,请先使用展开运算符进行拷贝:
const safeReversed = [...originalArray].reverse();
1.27 Array.prototype.shift() - 破坏性
Array.prototype.shift() 是 pop() 的“对手”。它的核心作用是:从数组中删除第一个元素(索引为 0 的元素),并返回该元素的值。
1.27.1 语法与参数
const firstElement = arr.shift();
参数:无。
返回值:
返回从数组中删除的那个元素。
如果数组是空的,则返回 undefined。
副作用:此方法会改变原数组,并自动更新剩余元素的索引(所有元素向前移动一位)。
处理一组按顺序到达的任务,处理完一个就踢出一个:
const taskQueue = ['download_file', 'extract_zip', 'cleanup'];
while (taskQueue.length > 0) {
const currentTask = taskQueue.shift();
console.log(`正在处理: ${currentTask}`);
}
1.27.2 避坑指南
① 性能警告 (O(n))
这是 shift() 和 pop() 最大的区别。
pop() 删除末尾元素,不影响其他元素。
shift() 删除首位元素后,数组必须重新排列剩下的每一个元素以填补 0 号索引的空缺。
建议:在处理包含数万个元素的超大型数组时,频繁使用 shift() 会导致明显的性能下降。
② 破坏性
- 它会直接修改原数组的 length。
1.28 Array.prototype.slice()
它的核心作用是:提取原数组的一部分,并将其作为一个新数组返回。
最关键的一点是:slice() 不会修改原数组(非破坏性)。
1.28.1 语法与参数
const newArray = arr.slice(begin, end);
begin (可选):从该索引开始提取(包含该索引)。
如果是负数,表示从末尾开始算起(例如 -2 表示倒数第二个)。
如果省略,默认为 0。
end (可选):在该索引处结束提取(不包含该索引)(左闭右开)。
如果是负数,表示从末尾开始算起(例如 -1 表示提取到倒数第一个之前)。
如果省略,默认提取到数组末尾。
返回值:包含提取元素的一个新数组。
const animals = ['ant', 'bison', 'camel', 'duck', 'elephant']; console.log(animals.slice(2)); // ["camel", "duck", "elephant"] console.log(animals.slice(2, 4)); // ["camel", "duck"] (不包含索引4) console.log(animals.slice(-2)); // ["duck", "elephant"] (取最后两个)
浅拷贝整个数组
这是在 ES6 展开运算符 ([…]) 流行之前,克隆数组最标准的方法:
const original = [1, 2, 3]; const clone = original.slice(); // clone 是一个新数组,修改它不会影响 original
1.28.2 避坑指南
① 浅拷贝陷阱
- slice() 执行的是浅拷贝。如果数组元素是对象,新数组和原数组引用的依然是同一个对象内存地址。修改对象内部属性会两边同步。
② 参数的**“左闭右开”**
- 永远记住:slice(start, end) 包含 start,但不包含 end。
1.29 Array.prototype.some()
它的核心作用是:测试数组中是否至少有一个元素通过了由提供的函数实现的测试。
它返回一个 布尔值,且具有 “短路”特性。
1.29.1 语法与参数
const hasMatch = arr.some(callbackFn(element, index, array), thisArg);
callbackFn (测试函数):为每个元素执行的函数。只要该函数对任意一个元素返回 true(真值),some() 就会立即停止遍历并返回 true。
返回值:布尔值 (true / false)。
逻辑等价:它相当于逻辑运算中的 “或”
检查用户组里是否有管理员:
const users = [
{ name: 'Tom', role: 'user' },
{ name: 'Jerry', role: 'admin' },
{ name: 'Spike', role: 'user' }
];
const hasAdmin = users.some(user => user.role === 'admin');
// true (在遍历到 Jerry 时就停止了)
替代 includes 查找复杂对象
includes() 只能找简单值或引用地址,而 some() 可以根据属性查找:
const fruits = [{ id: 1, name: 'apple' }, { id: 2, name: 'banana' }];
const hasApple = fruits.some(f => f.name === 'apple'); // true
1.29.2 避坑指南
① 空数组陷阱
- 对于空数组,some() 永远返回 false。因为没有任何元素能通过测试。
② 短路机制 (Short-circuiting)
- some() 一旦碰到返回 true 的回调,剩下的元素就不会再被处理了。如果你在回调里写了副作用操作(如打印日志),你会发现后面的日志没出来。
③ 非破坏性
- 它不会修改原数组。
1.30 Array.prototype.sort() - 破坏性
对数组元素进行原地(In-place)排序
1.30.1 语法与参数
arr.sort(compareFn);
compareFn (可选):定义排序顺序的函数。如果省略,数组元素将按照转换为字符串后的 Unicode 位点(Code points)进行排序。
返回值:指向原数组的引用(排序后的数组)。
副作用:破坏性方法。它会直接修改原数组。
核心痛点:为什么 [10, 2].sort() 会变成 [10, 2]?
这是新手最常遇到的坑。默认情况下,sort() 会把元素当成字符串处理:
“10” 的第一个字符是 “1”。
“2” 的第一个字符是 “2”。
因为 “1” 的字符编码比 “2” 小,所以 10 排在 2 前面。
引入意义: 为了正确排序数字或自定义对象,你必须提供一个比较函数 compareFn。
比较函数 compareFn(a, b) 的逻辑
该函数应返回一个数字,告诉引擎如何交换位置:
小于 0:a 排在 b 前面。
等于 0:保持相对位置不变。
大于 0:b 排在 a 前面。
const numbers = [10, 5, 8, 1, 7]; // 升序排列 numbers.sort((a, b) => a - b); // [1, 5, 7, 8, 10] // 降序排列 numbers.sort((a, b) => b - a); // [10, 8, 7, 5, 1]
1.30.2 避坑指南
① 破坏性
- 由于它直接修改原数组,在 React 等环境中,建议先拷贝再排序:
const sorted = [...originalArray].sort();
② 稳定性 (Stability)
- 从 ES2019 开始,JavaScript 规范要求 sort() 必须是稳定排序。这意味着如果两个元素相等,它们在排序后的相对顺序必须与排序前一致。
1.31 Array.prototype.splice() - 破坏性
它的核心作用是:在数组的 任意位置 进行 删除、替换或添加元素。
1.31.1 语法与参数
const deletedItems = arr.splice(start, deleteCount, item1, item2, ...);
- start (必填):指定修改的开始位置(索引)。
- 如果是负数,则从数组末尾开始计算。
- deleteCount (可选):整数,表示要移除的数组元素的个数。
- 如果设置为 0,则不删除元素(通常用于纯插入)。
- item1, item2, … (可选):要添加进数组的元素,从 start 位置开始。
- 返回值:一个包含被删除元素的新数组。如果没有删除任何元素,则返回空数组 []。
A. 删除元素
从索引 2 开始删除 1 个元素:
const fruits = ['apple', 'banana', 'orange', 'mango']; fruits.splice(2, 1); // fruits 变为: ['apple', 'banana', 'mango']
B. 插入元素
在索引 2 的位置插入 ‘lemon’,不删除任何东西:
const fruits = ['apple', 'banana', 'mango']; fruits.splice(2, 0, 'lemon'); // fruits 变为: ['apple', 'banana', 'lemon', 'mango']
C. 替换元素
删除 1 个并原地填补 1 个:
const fruits = ['apple', 'banana', 'mango']; fruits.splice(1, 1, 'strawberry'); // fruits 变为: ['apple', 'strawberry', 'mango']
1.31.2 避坑指南
分不清 splice 和 slice?
Splice 中的 P 可以记作 Permanent(永久的)或 Patch(打补丁),因为它会永久修改原数组。
Slice 可以记作 Share(分享),它只是切一块出来分享给你,原件不动。
性能开销:由于 splice 涉及到删除和插入,在数组中间操作会导致后续所有元素重排索引,在处理超大型数组时,性能开销比 pop 或 push 大。
负数索引:如果你写 arr.splice(-1, 1),它会非常方便地删掉数组的最后一个元素。
1.32 Array.prototype.toLocaleString()
它的核心作用是:将数组中的每个元素根据特定的语言环境(如时区、货币格式、数字分隔符)转换成字符串,并用逗号连接。
1.32.1 语法与参数
const str = arr.toLocaleString(locales, options);
locales (可选):一个带有 BCP 47 语言标签的字符串(如 ‘zh-CN’、‘en-US’)。
options (可选):一个配置对象,用于定义数字、日期或货币的具体格式。
返回值:根据本地习惯格式化后的字符串。
为什么要使用它?
普通的 toString() 或 join() 只是机械地把值转成字符串。但在处理金钱、日期或大数字时,不同国家有不同的写法。
- 引入意义:它让前端开发者无需手动编写复杂的正则或逻辑,就能轻松实现国际化(i18n)。
货币转换
这是它最强大的地方之一,可以配合 options 自动添加货币符号:
const prices = [100.5, 200, 3000];
const formatted = prices.toLocaleString('zh-CN', {
style: 'currency',
currency: 'CNY'
});
// "¥100.50,¥200.00,¥3,000.00"
日期数组本地化
const dates = [new Date(), new Date('2026-01-01')];
console.log(dates.toLocaleString('zh-CN'));
// "2026/2/27 13:56:43, 2026/1/1 08:00:00"
1.33 Array.prototype.toReversed()
Array.prototype.toReversed() 是 ES2023 (ES14) 引入的新特性。它的核心作用是:返回一个元素顺序颠倒的新数组,而 保持原数组不变。
1.33.1 语法与参数
const reversedArray = arr.toReversed();
参数:无。
返回值:一个新的数组,其元素顺序与原数组相反。
对原数组的影响:无(它是非破坏性的)。
const original = [1, 2, 3]; const reversed = original.toReversed(); console.log(original); // [1, 2, 3] <- 稳如泰山 console.log(reversed); // [3, 2, 1] <- 得到想要的结果
1.33.2 避坑指南
浅拷贝:和 slice() 一样,toReversed() 执行的是浅拷贝。如果数组里存的是对象,新旧数组引用的依然是同一个对象。
处理稀疏数组:toReversed() 在处理空洞(holes)时,会将空洞转换为 undefined,而传统的 reverse() 会保留空洞。
1.34 Array.prototype.toSorted()
它是经典 sort() 方法的“纯函数”版本,其核心作用是:返回一个排序后的新数组,而完全不修改原始数组。
1.34.1 语法与参数
const sortedArray = arr.toSorted(compareFn);
compareFn (可选):与 sort() 的比较函数完全一致。如果省略,依然会默认按字符串 Unicode 位点排序。
返回值:一个新的数组,其中的元素已按指定顺序排列。
对原数组的影响:无(非破坏性)。
const numbers = [10, 5, 8]; // 错误写法:const s = numbers.sort(); 这会改掉 numbers const sorted = [...numbers].sort((a, b) => a - b);
const numbers = [10, 5, 8]; const sorted = numbers.toSorted((a, b) => a - b); console.log(numbers); // [10, 5, 8] (原封不动) console.log(sorted); // [5, 8, 10] (得到新数组)
1.34.2 避坑指南
① 依然需要比较函数
- 它并没有解决 [10, 2].sort() 变成 [10, 2] 的逻辑问题。对于数字数组,你依然需要传入 (a, b) => a - b。
② 处理稀疏数组
- 这是它与 sort() 的细微差别:toSorted() 会将数组中的空洞(holes)转换为 undefined,并像对待普通元素一样进行排序(通常排在最后)。
1.35 Array.prototype.toSpliced()
它是经典 splice() 的“克隆版”,核心作用是:返回一个执行了删除、替换或插入操作后的新数组,而保持原数组完全不变。
1.35.1 语法与参数
const newArray = arr.toSpliced(start, deleteCount, item1, item2, ...);
参数:与 splice() 完全一致。
start:开始索引。
deleteCount:删除的元素个数。
item1…:要插入的新元素。
返回值:修改后的完整新数组。
对原数组的影响:无(非破坏性)。
const months = ["Jan", "Mar", "Apr"]; const fullMonths = months.toSpliced(1, 0, "Feb"); console.log(months); // ["Jan", "Mar", "Apr"] (原封不动) console.log(fullMonths); // ["Jan", "Feb", "Mar", "Apr"]
const numbers = [10, 20, 30, 40]; const result = numbers .toSpliced(1, 1, 25) // 把 20 换成 25 .map(n => n * 2); // [20, 50, 60, 80]
1.35.2 避坑指南
- 返回值差异(重点):splice() → \rightarrow → 返回 [被删除的元素]。toSpliced() → \rightarrow → 返回 [修改后的全量新数组]。
- 浅拷贝:与其它“to”系列方法一样,它执行的是浅拷贝。如果数组内是对象,引用关系依然存在。
1.36 Array.prototype.toString()
Array.prototype.toString() 是 JavaScript 数组最基础的类型转换方法。它的核心作用是:将数组中的所有元素转换成字符串,并用 逗号 连接成一个长字符串。
1.36.1 语法与参数
const resultString = arr.toString();
参数:无。
返回值:一个表示数组所有元素的字符串。
副作用:非破坏性。它不会改变原数组。
const arr = [1, 2, 'a', 'b']; console.log(arr.toString()); // "1,2,a,b" const dateArr = [new Date(2026, 0, 1)]; console.log(dateArr.toString()); // "Thu Jan 01 2026 00:00:00 GMT..."
1.36.2 避坑指南
A. 嵌套数组会被“拍平”
- 这是 toString() 一个比较独特的特性(有时也是坑)。它会递归地调用嵌套数组的 toString,导致多维数组看起来像一维数组的字符串:
const nested = [1, [2, 3], [4, [5]]]; console.log(nested.toString()); // "1,2,3,4,5"
- 如果你需要保留数组结构(如 JSON 格式),请使用 JSON.stringify(arr)。
B. null 和 undefined 的特殊处理
- 在数组中,null 和 undefined 会被转换为空字符串:
const arr = [1, null, 3, undefined]; console.log(arr.toString()); // "1,,3,"
自动类型转换
- 在 JavaScript 的隐式转换中,toString() 经常在幕后工作。例如,当数组与字符串相加时:
const arr = [1, 2, 3];
console.log("Result: " + arr); // "Result: 1,2,3"
// 实际上发生了: "Result: " + arr.toString()
1.37 Array.prototype.unshift() - 破坏性
Array.prototype.unshift() 是 push() 的“镜像”方法。它的核心作用是:将一个或多个元素添加到数组的开头。
1.37.1 语法与参数
const newLength = arr.unshift(element1, element2, ..., elementN);
参数:想要添加到数组开头的元素。
返回值:添加新元素后数组的最新长度 (length)。
副作用:此方法会改变原数组(原地修改)。
const numbers = [3, 4]; numbers.unshift(1, 2); // [1, 2, 3, 4]
1.37.2 避坑指南
① 返回值是长度
- 和 push 一样,它返回的是数组的新长度,而不是修改后的数组本身。
const result = [2, 3].unshift(1); console.log(result); // 3 (数组长度)
② 破坏性
- 它会直接修改你的原始变量。在 React 等需要“不可变数据”的场景,建议使用展开运算符:
const newArr = [newItem, ...oldArr];
1.38 Array.prototype.values()
它的核心作用是:返回一个新的数组迭代器(Array Iterator)对象,该对象包含了数组中每个索引对应的值。
1.38.1 语法与参数
const iterator = arr.values();
参数:无。
返回值:一个新的 Array Iterator 对象。
特性:它不存储实际的值,而是提供了一种按序访问原数组值的方式。
A. 配合 for…of 循环
这是最自然的使用方式(尽管直接遍历数组更常见,但 values() 让语义更显式):
const fruits = ['apple', 'banana', 'cherry'];
const iterator = fruits.values();
for (const value of iterator) {
console.log(value);
}
// 输出: apple, banana, cherry
B. 手动控制遍历进度
你可以通过调用 .next() 逐步获取值,这在实现复杂的异步逻辑或生成器时非常有用:
const arr = ['a', 'b'];
const iter = arr.values();
console.log(iter.next()); // { value: 'a', done: false }
console.log(iter.next()); // { value: 'b', done: false }
console.log(iter.next()); // { value: undefined, done: true }
1.39 Array.prototype.with()
它的核心作用是:修改数组中指定索引的值,并返回一个新数组,而不改变原数组。
1.39.1 语法与参数
const newArray = arr.with(index, value);
index:要修改的索引(支持负数,-1 代表最后一个元素)。
value:要放入该位置的新值。
返回值:一个新的数组,其中指定索引处已被替换为新值。
对原数组的影响:无(非破坏性)。
const arr = [1, 2, 3]; // 旧方案 1:展开运算符 const newArr = [...arr]; newArr[1] = 99; // 旧方案 2:slice const newArr2 = arr.slice(0, 1).concat(99, arr.slice(2));
const temperatures = [22, 25, 21, 28]; const corrected = temperatures.with(2, 23); console.log(temperatures); // [22, 25, 21, 28] (原封不动) console.log(corrected); // [22, 25, 23, 28] (索引 2 已更新)
const players = ['Alice', 'Bob', 'Charlie']; const updated = players.with(-1, 'Zoe'); // ['Alice', 'Bob', 'Zoe']
1.39.2 避坑指南
超出索引范围:如果索引超出了数组范围(无论是正向还是负向),with() 会抛出 RangeError。
浅拷贝:与 toSorted 或 toReversed 一样,它执行的是浅拷贝。如果替换的是对象,虽然新数组指向了新引用,但未被替换的其他对象元素依然引用着同一份内存地址。
二、 Array 静态方法 (Static Methods)
2.1 Array.from()
将 类数组对象(Array-like)或 可迭代对象(Iterable)转换成一个真正的数组。
它是一个静态方法,必须通过 Array.from() 调用,而不是在数组实例上调用。
2.1.1 语法与参数
Array.from(arrayLike, mapFn, thisArg);
arrayLike (必填):想要转换的对象。
- 类数组:拥有 length 属性和索引元素的对象(如 arguments 或 NodeList)。
- 可迭代对象:实现了迭代协议的对象(如 Set、Map 或 String)。
mapFn (可选):新数组中的每个元素都会执行的回调函数,类似于 .map()。
返回值:一个新的数组实例。
A. 将 Set 或 Map 转为数组
常用于数组去重:
const set = new Set([1, 2, 2, 3]); const uniqueArr = Array.from(set); // [1, 2, 3]
B. 操作 DOM NodeList
让 querySelectorAll 获取的节点可以使用数组方法:
const divs = document.querySelectorAll('div');
const divIds = Array.from(divs, div => div.id);
C. 快速生成序列
这是 Array.from() 最精妙的用法之一,可以替代繁琐的 for 循环:
// 生成 0 到 4 的数组
const range = Array.from({ length: 5 }, (v, i) => i);
// [0, 1, 2, 3, 4]
2.1.2 避坑指南
① 浅拷贝
- Array.from() 创建的是原始数据的浅拷贝。如果转换的对象内部包含引用类型,新数组和原对象共享这些引用。
② 与展开运算符 ([…]) 的区别
[…] 只能转换可迭代对象(Iterable)。
Array.from() 还能转换类数组对象(只需有 length 属性即可)。
// 只有 Array.from 能处理这个:
const obj = { length: 2, 0: 'a', 1: 'b' };
console.log(Array.from(obj)); // ['a', 'b']
console.log([...obj]); // TypeError: obj is not iterable
2.2 Array.fromAsync()
它是 Array.from() 的异步孪生兄弟,专门用于处理 异步可迭代对象(Async Iterables),例如从网络流、数据库游标或异步生成器中获取数据。
2.2.1 语法与参数
const newArray = await Array.fromAsync(asyncIterable, mapFn, thisArg);
asyncIterable:一个异步可迭代对象(如 ReadableStream)、同步可迭代对象(如 Array、Set)或类数组对象。
mapFn (可选):对每个元素执行的函数。这个函数可以是异步的(返回 Promise),fromAsync 会等待它解析。
返回值:一个 Promise,解析后得到一个新的数组实例。
为什么要使用它?
在它出现之前,如果你想把一个异步迭代器转换为数组,你需要使用 for await…of 循环:
const arr = [];
for await (const item of asyncGen()) {
arr.push(item);
}
A. 转换异步生成器 (Async Generator)
async function* asyncGen() {
yield Promise.resolve(1);
yield 2;
yield 3;
}
const arr = await Array.fromAsync(asyncGen());
console.log(arr); // [1, 2, 3]
2.3 Array.isArray()
Array.isArray() 是判断一个变量是否为数组的终极权威方法。它的核心作用是:可靠地检测传入的值是否是一个真正的 Array 实例。
2.3.1 语法与参数
const result = Array.isArray(value);
- 返回值:如果是数组则返回 true,否则返回 false(即使是类数组对象或 null 也会返回 false)。
为什么不用 typeof?
在 JavaScript 的历史中,检测数组一直是个麻烦事:
typeof 的局限:typeof [] 会返回 “object”。这让你无法区分它是普通对象、数组还是 null。
instanceof 的跨环境陷阱:在网页开发中,如果你有多个 iframe,每个 iframe 都有自己的全局环境(和自己的 Array 构造函数)。从一个 iframe 传到另一个 iframe 的数组,在主页面的 instanceof Array 检测中会失效。
引入意义: Array.isArray() 能够跨越 iframe 或不同的执行上下文,始终准确地识别数组。
// 全部为 true
Array.isArray([]);
Array.isArray([1, 2]);
Array.isArray(new Array());
Array.isArray(Array.prototype); // 没错,原型也是数组
// 全部为 false
Array.isArray();
Array.isArray({});
Array.isArray(null);
Array.isArray(undefined);
Array.isArray(17);
Array.isArray('Array');
Array.isArray(new Uint8Array(10)); // 类型化数组不是普通数组
2.4 Array.of()
创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。
2.4.1 语法与参数
Array.of(element0, element1, ..., elementN);
参数:任意数量的元素,这些元素将按顺序成为新数组的成员。
返回值:一个新的 Array 实例。
为什么要使用它?(解决 Array 构造函数的陷阱)
这是 Array.of() 存在的唯一且最重要的原因。传统的 Array() 构造函数在处理单个数字参数时存在一个非常迷惑的行为:
new Array(7):创建一个长度为 7 的空数组(只有空位,没有元素)。
Array.of(7):创建一个包含单个元素 7 的数组 [7]。
引入意义:它提供了一个行为始终统一的数组创建方式,无论你传入的是什么、传入了多少个。
A. 修正构造函数歧义
当你需要动态创建一个包含单个数字的数组时,Array.of() 是最安全的选择:
const size = 7; const a = new Array(size); // [empty × 7] const b = Array.of(size); // [7]
2.4.2 避坑指南
与 Array.from() 的区别:
Array.from() 是把“像数组的东西”转换成数组。
Array.of() 是把“零散的参数”组合成数组。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。


最新评论