JS之Array构造函数上的静态方法与实例解读

 更新时间:2026年04月11日 14:57:29   作者:Hello--_--World  
本文详细介绍了JavaScript Array对象的实例方法和静态方法,包括Array.prototype.at()、concat()、entries()、every()等实例方法,以及from()、fromAsync()、isArray()、of()等静态方法,实例方法主要用于数组操作和处理,如查找、合并、转换等

在 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() 是把“零散的参数”组合成数组。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • layer扩展打开/关闭动画的方法

    layer扩展打开/关闭动画的方法

    今天小编就为大家分享一篇layer扩展打开/关闭动画的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-09-09
  • JS使用链式属性表达式取值和赋值的实现方法

    JS使用链式属性表达式取值和赋值的实现方法

    这篇文章主要给大家详细介绍了JS如何使用链式属性表达式取值和赋值,文章通过代码示例介绍的非常详细,对我们的学习或工作有一定的帮助,感兴趣的同学可以参考一下
    2023-08-08
  • js实现轮播图制作方法

    js实现轮播图制作方法

    这篇文章主要为大家详细介绍了js实现轮播图的制作方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-06-06
  • JavaScript中的全局属性与方法深入解析

    JavaScript中的全局属性与方法深入解析

    这篇文章主要给大家介绍了关于JavaScript中全局属性与方法的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用JavaScript具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2020-06-06
  • JS实现的一个简单的Autocomplete自动完成例子

    JS实现的一个简单的Autocomplete自动完成例子

    这篇文章主要介绍了JS实现的一个简单的Autocomplete自动完成例子,需要的朋友可以参考下
    2014-04-04
  • js实现精确到毫秒的倒计时效果

    js实现精确到毫秒的倒计时效果

    这篇文章主要为大家详细介绍了js实现精确到毫秒的倒计时效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-08-08
  • 微信小程序教程系列之页面跳转和参数传递(6)

    微信小程序教程系列之页面跳转和参数传递(6)

    这篇文章主要为大家详细介绍了微信小程序教程系列之页面跳转和参数传递,微信小程序提供了3种页面跳转方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-04-04
  • JavaScript touch触摸事件的使用讲解

    JavaScript touch触摸事件的使用讲解

    这篇文章主要介绍了JavaScript touch触摸事件的使用,JavaScript中的触摸事件对象TouchEvent是处理移动设备触摸交互的核心接口,承载了触摸操作的所有相关信息,需要的朋友可以参考下
    2026-01-01
  • JavaScript实现网页头部进度条刷新

    JavaScript实现网页头部进度条刷新

    这篇文章主要介绍了JavaScript实现网页头部进度条刷新实例代码,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2017-04-04
  • js判断浏览器是否支持严格模式的方法

    js判断浏览器是否支持严格模式的方法

    除了正常运行模式,ECMAscript 5添加了第二种运行模式:"严格模式"(strict mode)。顾名思义,这种模式使得Javascript在更严格的条件下运行。这篇文章给大家详细介绍了js判断浏览器是否支持严格模式的方法,有需要的朋友们可以参考借鉴。
    2016-10-10

最新评论