Array

Baseline 广泛可用 *

此特性已相当成熟,可在许多设备和浏览器版本上使用。自 ⁨2015 年 7 月⁩以来,各浏览器均已提供此特性。

* 此特性的某些部分可能存在不同级别的支持。

与其它编程语言中的数组一样,Array 对象能够在单个变量名下存储多个项目的集合,并且具有用于执行常见数组操作的成员。

描述

在 JavaScript 中,数组不是原始值,而是具有以下核心特征的 Array 对象:

  • JavaScript 数组可调整大小,并且可以包含不同数据类型的混合。(当这些特性不适用时,请改用类型化数组。)
  • JavaScript 数组不是关联数组,因此数组元素不能使用任意字符串作为索引进行访问,而必须使用非负整数(或其各自的字符串形式)作为索引进行访问。
  • JavaScript 数组是零索引的:数组的第一个元素位于索引 0 处,第二个元素位于索引 1 处,依此类推——最后一个元素位于数组的length 属性值减去 1 处。
  • JavaScript 数组复制操作会创建浅拷贝。(所有带有任何 JavaScript 对象的标准内置复制操作都会创建浅拷贝,而不是深拷贝。)

数组索引

Array 对象不能使用任意字符串作为元素索引(如在关联数组中),而必须使用非负整数(或其各自的字符串形式)。通过非整数设置或访问不会从数组列表本身设置或检索元素,而是会设置或访问与该数组的对象属性集合关联的变量。数组的对象属性和数组元素列表是分开的,数组的遍历和变异操作不能应用于这些命名属性。

数组元素是对象属性,就像 toString 是属性一样(但具体来说,toString() 是一个方法)。然而,尝试按如下方式访问数组元素会抛出语法错误,因为属性名无效:

js
arr.0; // a syntax error

JavaScript 语法要求以数字开头的属性使用方括号表示法而不是点表示法访问。也可以引用数组索引(例如,years['2'] 而不是 years[2]),尽管通常不需要。

years[2] 中的 2 通过隐式 toString 转换被 JavaScript 引擎强制转换为字符串。因此,'2''02' 将指向 years 对象上的两个不同槽位,以下示例可能为 true

js
console.log(years["2"] !== years["02"]);

只有 years['2'] 是实际的数组索引。years['02'] 是一个任意字符串属性,在数组迭代中不会被访问。

长度与数字属性的关系

JavaScript 数组的length 属性和数字属性是相互关联的。

在调用时,一些内置数组方法(例如,join()slice()indexOf() 等)会考虑数组的length 属性值。

其他方法(例如,push()splice() 等)也会导致数组的length 属性更新。

js
const fruits = [];
fruits.push("banana", "apple", "peach");
console.log(fruits.length); // 3

当在 JavaScript 数组上设置属性时,如果该属性是有效的数组索引且该索引超出数组当前边界,引擎将相应地更新数组的length 属性:

js
fruits[5] = "mango";
console.log(fruits[5]); // 'mango'
console.log(Object.keys(fruits)); // ['0', '1', '2', '5']
console.log(fruits.length); // 6

增加length 会通过添加空槽位来扩展数组,而不会创建任何新元素——甚至不是 undefined

js
fruits.length = 10;
console.log(fruits); // ['banana', 'apple', 'peach', empty x 2, 'mango', empty x 4]
console.log(Object.keys(fruits)); // ['0', '1', '2', '5']
console.log(fruits.length); // 10
console.log(fruits[8]); // undefined

然而,减少length 属性会删除元素。

js
fruits.length = 2;
console.log(Object.keys(fruits)); // ['0', '1']
console.log(fruits.length); // 2

这在length 页面上有进一步解释。

数组方法和空槽位

在遇到稀疏数组中的空槽位时,数组方法有不同的行为。一般来说,较旧的方法(例如 forEach)将空槽位与包含 undefined 的索引区别对待。

对空槽位进行特殊处理的方法包括:concat()copyWithin()every()filter()flat()flatMap()forEach()indexOf()lastIndexOf()map()reduce()reduceRight()reverse()slice()some()sort()splice()。像 forEach 这样的迭代方法根本不会访问空槽位。其他方法,例如 concatcopyWithin 等,在复制时会保留空槽位,因此最终数组仍然是稀疏的。

js
const colors = ["red", "yellow", "blue"];
colors[5] = "purple";
colors.forEach((item, index) => {
  console.log(`${index}: ${item}`);
});
// Output:
// 0: red
// 1: yellow
// 2: blue
// 5: purple

colors.reverse(); // ['purple', empty × 2, 'blue', 'yellow', 'red']

较新的方法(例如 keys)不会特殊处理空槽位,并将它们视为包含 undefined。将空槽位与 undefined 元素混淆的方法包括:entries()fill()find()findIndex()findLast()findLastIndex()includes()join()keys()toLocaleString()toReversed()toSorted()toSpliced()values()with()

js
const colors = ["red", "yellow", "blue"];
colors[5] = "purple";
const iterator = colors.keys();
for (const key of iterator) {
  console.log(`${key}: ${colors[key]}`);
}
// Output
// 0: red
// 1: yellow
// 2: blue
// 3: undefined
// 4: undefined
// 5: purple

const newColors = colors.toReversed(); // ['purple', undefined, undefined, 'blue', 'yellow', 'red']

复制方法和变异方法

有些方法不会改变调用该方法的现有数组,而是返回一个新数组。它们通过首先构造一个新数组,然后用元素填充它来实现。复制总是浅层的——方法永远不会复制超出最初创建的数组的任何内容。原始数组的元素按以下方式复制到新数组中:

  • 对象:对象引用被复制到新数组中。原始数组和新数组都引用相同的对象。也就是说,如果引用的对象被修改,则更改对新数组和原始数组都可见。
  • 字符串、数字和布尔值等原始类型(不是StringNumberBoolean 对象):它们的值被复制到新数组中。

其他方法会改变调用该方法的数组,在这种情况下,它们的返回值因方法而异:有时是同一数组的引用,有时是新数组的长度。

以下方法通过访问 this.constructor[Symbol.species] 来确定要使用的构造函数,从而创建新数组:concat()filter()flat()flatMap()map()slice()splice()(用于构造返回的已删除元素数组)。

以下方法总是使用 Array 基本构造函数创建新数组:toReversed()toSorted()toSpliced()with()

下表列出了会改变原始数组的方法,以及相应的非变异替代方法:

变异方法 非变异替代方法
copyWithin() 没有单方法替代
fill() 没有单方法替代
pop() slice(0, -1)
push(v1, v2) concat([v1, v2])
reverse() toReversed()
shift() slice(1)
sort() toSorted()
splice() toSpliced()
unshift(v1, v2) toSpliced(0, 0, v1, v2)

将变异方法转换为非变异替代方法的简单方法是,首先使用展开语法slice() 来创建副本:

js
arr.copyWithin(0, 1, 2); // mutates arr
const arr2 = arr.slice().copyWithin(0, 1, 2); // does not mutate arr
const arr3 = [...arr].copyWithin(0, 1, 2); // does not mutate arr

迭代方法

许多数组方法都接受回调函数作为参数。回调函数按顺序调用,并且数组中的每个元素最多调用一次,回调函数的返回值用于确定方法的返回值。它们都具有相同的签名:

js
method(callbackFn, thisArg)

其中 callbackFn 接受三个参数:

element

数组中正在处理的当前元素。

index

数组中正在处理的当前元素的索引。

array

调用该方法的数组。

callbackFn 应该返回什么取决于所调用的数组方法。

调用 callbackFn 时,thisArg 参数(默认为 undefined)将用作 this 值。callbackFn 最终可观察到的 this 值根据常规规则确定:如果 callbackFn 不是严格模式,原始 this 值将封装为对象,并且 undefined/null 将替换为globalThisthisArg 参数与使用箭头函数定义的任何 callbackFn 无关,因为箭头函数没有自己的 this 绑定

传递给 callbackFnarray 参数在迭代期间读取另一个索引时最有用,因为您可能并不总是有一个现有变量引用当前数组。您通常不应该在迭代期间改变数组(请参阅迭代方法中改变初始数组),但您也可以使用此参数来执行此操作。在像 map()filter()flatMap() 这样的方法中,array 参数不是正在构建的数组——无法从回调函数访问正在构建的数组。

所有迭代方法都是复制的通用的,尽管它们在处理空槽位时行为不同。

以下方法是迭代方法:every()filter()find()findIndex()findLast()findLastIndex()flatMap()forEach()map()some()

特别是,every()find()findIndex()findLast()findLastIndex()some() 并不总是对每个元素调用 callbackFn——它们会在确定返回值后立即停止迭代。

reduce()reduceRight() 方法也接受回调函数,并且对于数组中的每个元素最多运行一次,但它们的签名与典型的迭代方法略有不同(例如,它们不接受 thisArg)。

sort() 方法也接受回调函数,但它不是迭代方法。它会就地改变数组,不接受 thisArg,并且可能会在同一索引上多次调用回调。

迭代方法按以下方式迭代数组(省略了许多技术细节):

js
function method(callbackFn, thisArg) {
  const length = this.length;
  for (let i = 0; i < length; i++) {
    if (i in this) {
      const result = callbackFn.call(thisArg, this[i], i, this);
      // Do something with result; maybe return early
    }
  }
}

请注意以下事项:

  1. 并非所有方法都执行 i in this 测试。findfindIndexfindLastfindLastIndex 方法不执行此测试,但其他方法执行。
  2. length 在循环开始前被记忆。这会影响迭代期间的插入和删除处理方式(参见迭代方法中改变初始数组)。
  3. 该方法不记忆数组内容,因此如果在迭代期间修改任何索引,则可能会观察到新值。
  4. 上面的代码按索引升序迭代数组。某些方法按索引降序迭代(for (let i = length - 1; i >= 0; i--)):reduceRight()findLast()findLastIndex()
  5. reducereduceRight 具有略微不同的签名,并且并不总是从第一个/最后一个元素开始。

通用数组方法

数组方法总是通用的——它们不访问数组对象的任何内部数据。它们只通过 length 属性和索引元素访问数组元素。这意味着它们也可以在类数组对象上调用。

js
const arrayLike = {
  0: "a",
  1: "b",
  length: 2,
};
console.log(Array.prototype.join.call(arrayLike, "+")); // 'a+b'

长度属性的标准化

length 属性被转换为整数,然后钳制到 0 和 253 - 1 之间的范围。NaN 变为 0,因此即使 length 不存在或为 undefined,它的行为也像其值为 0 一样。

该语言避免将 length 设置为不安全整数。如果 length 将设置为大于 253 - 1 的数字,则所有内置方法都将抛出 TypeError。然而,由于数组的length 属性在设置为大于 232 - 1 时会抛出错误,因此通常不会达到安全整数阈值,除非在非数组对象上调用该方法。

js
Array.prototype.flat.call({}); // []

一些数组方法设置数组对象的 length 属性。它们总是在标准化后设置值,因此 length 总是以整数结束。

js
const a = { length: 0.7 };
Array.prototype.push.call(a);
console.log(a.length); // 0

类数组对象

术语类数组对象指的是在上面描述的 length 转换过程中不会抛出错误的任何对象。实际上,此类对象应实际具有 length 属性,并且在 0length - 1 范围内具有索引元素。(如果它没有所有索引,它将在功能上等同于稀疏数组。)当数组方法作用于类数组对象时,任何小于零或大于 length - 1 的整数索引都将被忽略。

许多 DOM 对象都是类数组的——例如,NodeListHTMLCollectionarguments 对象也是类数组的。您可以对它们调用数组方法,即使它们本身没有这些方法。

js
function f() {
  console.log(Array.prototype.join.call(arguments, "+"));
}

f("a", "b"); // 'a+b'

构造函数

Array()

创建一个新的 Array 对象。

静态属性

Array[Symbol.species]

返回 Array 构造函数。

静态方法

Array.from()

从可迭代对象或类数组对象创建一个新的 Array 实例。

Array.fromAsync()

从异步可迭代对象、可迭代对象或类数组对象创建一个新的 Array 实例。

Array.isArray()

如果参数是数组,则返回 true,否则返回 false

Array.of()

根据参数的数量或类型,使用可变数量的参数创建一个新的 Array 实例。

实例属性

这些属性在 Array.prototype 上定义,并由所有 Array 实例共享。

Array.prototype.constructor

创建实例对象的构造函数。对于 Array 实例,初始值是 Array 构造函数。

Array.prototype[Symbol.unscopables]

包含在 ES2015 版本之前未包含在 ECMAScript 标准中,并且在 with 语句绑定目的中被忽略的属性名称。

这些属性是每个 Array 实例的自有属性。

length

反映数组中元素的数量。

实例方法

Array.prototype.at()

返回给定索引处的数组项。接受负整数,从最后一项开始倒数。

Array.prototype.concat()

返回一个新数组,它是调用数组与其它数组和/或值连接的结果。

Array.prototype.copyWithin()

在数组内部复制一系列数组元素。

Array.prototype.entries()

返回一个包含数组中每个索引的键/值对的新数组迭代器对象。

Array.prototype.every()

如果它在数组中找到一个不满足提供的测试函数的元素,则返回 false。否则,它返回 true

Array.prototype.fill()

用静态值填充数组从起始索引到结束索引的所有元素。

Array.prototype.filter()

返回一个新数组,其中包含调用数组中所有满足提供的过滤函数返回 true 的元素。

Array.prototype.find()

返回数组中满足提供的测试函数的第一个元素的值,如果没有找到合适的元素,则返回 undefined

Array.prototype.findIndex()

返回数组中满足提供的测试函数的第一个元素的索引,如果没有找到合适的元素,则返回 -1

Array.prototype.findLast()

返回数组中满足提供的测试函数的最后一个元素的值,如果没有找到合适的元素,则返回 undefined

Array.prototype.findLastIndex()

返回数组中满足提供的测试函数的最后一个元素的索引,如果没有找到合适的元素,则返回 -1

Array.prototype.flat()

返回一个新数组,其中所有子数组元素都递归连接到指定的深度。

Array.prototype.flatMap()

返回一个新数组,通过对调用数组的每个元素应用给定的回调函数,然后将结果展平一层。

Array.prototype.forEach()

对调用数组中的每个元素调用一个函数。

Array.prototype.includes()

确定调用数组是否包含某个值,返回 truefalse

Array.prototype.indexOf()

返回给定元素在调用数组中首次出现(最小)的索引。

Array.prototype.join()

将数组的所有元素连接成一个字符串。

Array.prototype.keys()

返回一个包含调用数组中每个索引的键的新数组迭代器

Array.prototype.lastIndexOf()

返回给定元素在调用数组中最后一次出现(最大)的索引,如果没有找到,则返回 -1

Array.prototype.map()

返回一个新数组,其中包含对调用数组中的每个元素调用函数的结果。

Array.prototype.pop()

从数组中删除最后一个元素并返回该元素。

Array.prototype.push()

向数组的末尾添加一个或多个元素,并返回数组的新 length

Array.prototype.reduce()

对数组的每个元素(从左到右)执行用户提供的“reducer”回调函数,将其归结为单个值。

Array.prototype.reduceRight()

对数组的每个元素(从右到左)执行用户提供的“reducer”回调函数,将其归结为单个值。

Array.prototype.reverse()

就地反转数组元素的顺序。(第一个变为最后一个,最后一个变为第一个。)

Array.prototype.shift()

从数组中删除第一个元素并返回该元素。

Array.prototype.slice()

提取调用数组的一部分并返回一个新数组。

Array.prototype.some()

如果它在数组中找到一个满足提供的测试函数的元素,则返回 true。否则,它返回 false

Array.prototype.sort()

就地排序数组的元素并返回数组。

Array.prototype.splice()

从数组中添加和/或删除元素。

Array.prototype.toLocaleString()

返回表示调用数组及其元素的本地化字符串。覆盖 Object.prototype.toLocaleString() 方法。

Array.prototype.toReversed()

返回一个元素顺序反转的新数组,而不修改原始数组。

Array.prototype.toSorted()

返回一个元素按升序排序的新数组,而不修改原始数组。

Array.prototype.toSpliced()

返回一个新数组,其中在给定索引处删除和/或替换了一些元素,而不修改原始数组。

Array.prototype.toString()

返回表示调用数组及其元素的字符串。覆盖 Object.prototype.toString() 方法。

Array.prototype.unshift()

向数组的开头添加一个或多个元素,并返回数组的新 length

Array.prototype.values()

返回一个包含数组中每个索引的值的新数组迭代器对象。

Array.prototype.with()

返回一个新数组,其中给定索引处的元素替换为给定值,而不修改原始数组。

Array.prototype[Symbol.iterator]()

默认情况下是 values() 方法的别名。

示例

本节提供了一些 JavaScript 中常见数组操作的示例。

注意:如果您还不熟悉数组基础知识,请考虑首先阅读JavaScript 初学者:数组,其中解释了数组是什么,并包含了其他常见数组操作的示例。

创建数组

此示例展示了创建新数组的三种方法:首先使用数组字面量表示法,然后使用Array() 构造函数,最后使用String.prototype.split() 从字符串构建数组。

js
// 'fruits' array created using array literal notation.
const fruits = ["Apple", "Banana"];
console.log(fruits.length);
// 2

// 'fruits2' array created using the Array() constructor.
const fruits2 = new Array("Apple", "Banana");
console.log(fruits2.length);
// 2

// 'fruits3' array created using String.prototype.split().
const fruits3 = "Apple, Banana".split(", ");
console.log(fruits3.length);
// 2

从数组创建字符串

此示例使用join() 方法从 fruits 数组创建字符串。

js
const fruits = ["Apple", "Banana"];
const fruitsString = fruits.join(", ");
console.log(fruitsString);
// "Apple, Banana"

按索引访问数组项

此示例展示了如何通过指定数组中项的位置索引号来访问 fruits 数组中的项。

js
const fruits = ["Apple", "Banana"];

// The index of an array's first element is always 0.
fruits[0]; // Apple

// The index of an array's second element is always 1.
fruits[1]; // Banana

// The index of an array's last element is always one
// less than the length of the array.
fruits[fruits.length - 1]; // Banana

// Using an index number larger than the array's length
// returns 'undefined'.
fruits[99]; // undefined

在数组中查找项的索引

此示例使用indexOf() 方法查找字符串 "Banana"fruits 数组中的位置(索引)。

js
const fruits = ["Apple", "Banana"];
console.log(fruits.indexOf("Banana"));
// 1

检查数组是否包含某个项

此示例展示了两种检查 fruits 数组是否包含 "Banana""Cherry" 的方法:首先使用includes() 方法,然后使用indexOf() 方法测试非 -1 的索引值。

js
const fruits = ["Apple", "Banana"];

fruits.includes("Banana"); // true
fruits.includes("Cherry"); // false

// If indexOf() doesn't return -1, the array contains the given item.
fruits.indexOf("Banana") !== -1; // true
fruits.indexOf("Cherry") !== -1; // false

向数组追加项

此示例使用push() 方法向 fruits 数组追加一个新字符串。

js
const fruits = ["Apple", "Banana"];
const newLength = fruits.push("Orange");
console.log(fruits);
// ["Apple", "Banana", "Orange"]
console.log(newLength);
// 3

从数组中删除最后一项

此示例使用pop() 方法从 fruits 数组中删除最后一项。

js
const fruits = ["Apple", "Banana", "Orange"];
const removedItem = fruits.pop();
console.log(fruits);
// ["Apple", "Banana"]
console.log(removedItem);
// Orange

注意:pop() 只能用于删除数组中的最后一项。要从数组末尾删除多个项,请参见下一个示例。

从数组末尾删除多个项

此示例使用splice() 方法从 fruits 数组中删除最后 3 个项。

js
const fruits = ["Apple", "Banana", "Strawberry", "Mango", "Cherry"];
const start = -3;
const removedItems = fruits.splice(start);
console.log(fruits);
// ["Apple", "Banana"]
console.log(removedItems);
// ["Strawberry", "Mango", "Cherry"]

将数组截断为仅包含其前 N 个项

此示例使用splice() 方法将 fruits 数组截断为仅包含其前 2 个项。

js
const fruits = ["Apple", "Banana", "Strawberry", "Mango", "Cherry"];
const start = 2;
const removedItems = fruits.splice(start);
console.log(fruits);
// ["Apple", "Banana"]
console.log(removedItems);
// ["Strawberry", "Mango", "Cherry"]

从数组中删除第一项

此示例使用shift() 方法从 fruits 数组中删除第一项。

js
const fruits = ["Apple", "Banana"];
const removedItem = fruits.shift();
console.log(fruits);
// ["Banana"]
console.log(removedItem);
// Apple

注意:shift() 只能用于删除数组中的第一项。要从数组开头删除多个项,请参见下一个示例。

从数组开头删除多个项

此示例使用splice() 方法从 fruits 数组中删除前 3 个项。

js
const fruits = ["Apple", "Strawberry", "Cherry", "Banana", "Mango"];
const start = 0;
const deleteCount = 3;
const removedItems = fruits.splice(start, deleteCount);
console.log(fruits);
// ["Banana", "Mango"]
console.log(removedItems);
// ["Apple", "Strawberry", "Cherry"]

向数组添加一个新的第一项

此示例使用unshift() 方法在 fruits 数组的索引 0 处添加一个新项——使其成为数组中的新第一项。

js
const fruits = ["Banana", "Mango"];
const newLength = fruits.unshift("Strawberry");
console.log(fruits);
// ["Strawberry", "Banana", "Mango"]
console.log(newLength);
// 3

按索引删除单个项

此示例使用splice() 方法从 fruits 数组中删除字符串 "Banana"——通过指定 "Banana" 的索引位置。

js
const fruits = ["Strawberry", "Banana", "Mango"];
const start = fruits.indexOf("Banana");
const deleteCount = 1;
const removedItems = fruits.splice(start, deleteCount);
console.log(fruits);
// ["Strawberry", "Mango"]
console.log(removedItems);
// ["Banana"]

按索引删除多个项

此示例使用splice() 方法从 fruits 数组中删除字符串 "Banana""Strawberry"——通过指定 "Banana" 的索引位置,以及要删除的总项数的计数。

js
const fruits = ["Apple", "Banana", "Strawberry", "Mango"];
const start = 1;
const deleteCount = 2;
const removedItems = fruits.splice(start, deleteCount);
console.log(fruits);
// ["Apple", "Mango"]
console.log(removedItems);
// ["Banana", "Strawberry"]

替换数组中的多个项

此示例使用splice() 方法用新项替换 fruits 数组中的最后 2 个项。

js
const fruits = ["Apple", "Banana", "Strawberry"];
const start = -2;
const deleteCount = 2;
const removedItems = fruits.splice(start, deleteCount, "Mango", "Cherry");
console.log(fruits);
// ["Apple", "Mango", "Cherry"]
console.log(removedItems);
// ["Banana", "Strawberry"]

遍历数组

此示例使用 for...of 循环遍历 fruits 数组,将每个项记录到控制台。

js
const fruits = ["Apple", "Mango", "Cherry"];
for (const fruit of fruits) {
  console.log(fruit);
}
// Apple
// Mango
// Cherry

但是 for...of 只是遍历任何数组的众多方法之一;有关更多方法,请参阅循环和迭代,并参阅 every()filter()flatMap()map()reduce()reduceRight() 方法的文档——并参阅下一个示例,该示例使用forEach() 方法。

对数组中的每个元素调用函数

此示例使用forEach() 方法对 fruits 数组中的每个元素调用一个函数;该函数导致每个项以及该项的索引号被记录到控制台。

js
const fruits = ["Apple", "Mango", "Cherry"];
fruits.forEach((item, index, array) => {
  console.log(item, index);
});
// Apple 0
// Mango 1
// Cherry 2

合并多个数组

此示例使用concat() 方法将 fruits 数组与 moreFruits 数组合并,以生成一个新的 combinedFruits 数组。请注意,fruitsmoreFruits 保持不变。

js
const fruits = ["Apple", "Banana", "Strawberry"];
const moreFruits = ["Mango", "Cherry"];
const combinedFruits = fruits.concat(moreFruits);
console.log(combinedFruits);
// ["Apple", "Banana", "Strawberry", "Mango", "Cherry"]

// The 'fruits' array remains unchanged.
console.log(fruits);
// ["Apple", "Banana", "Strawberry"]

// The 'moreFruits' array also remains unchanged.
console.log(moreFruits);
// ["Mango", "Cherry"]

复制数组

此示例展示了从现有 fruits 数组创建新数组的三种方法:首先使用展开语法,然后使用from() 方法,然后使用slice() 方法。

js
const fruits = ["Strawberry", "Mango"];

// Create a copy using spread syntax.
const fruitsCopy = [...fruits];
// ["Strawberry", "Mango"]

// Create a copy using the from() method.
const fruitsCopy2 = Array.from(fruits);
// ["Strawberry", "Mango"]

// Create a copy using the slice() method.
const fruitsCopy3 = fruits.slice();
// ["Strawberry", "Mango"]

所有内置的数组复制操作(展开语法Array.from()Array.prototype.slice()Array.prototype.concat())都会创建浅拷贝。如果您希望对数组进行深拷贝,可以使用JSON.stringify() 将数组转换为 JSON 字符串,然后使用JSON.parse() 将字符串转换回一个完全独立于原始数组的新数组。

js
const fruitsDeepCopy = JSON.parse(JSON.stringify(fruits));

您还可以使用 structuredClone() 方法创建深拷贝,其优点是允许将源中的可转移对象转移到新副本中,而不仅仅是克隆。

最后,重要的是要理解将现有数组分配给新变量并不会创建数组或其元素的副本。相反,新变量只是原始数组的引用或别名;也就是说,原始数组的名称和新变量的名称只是同一个对象的两个名称(因此将始终被评估为严格相等)。因此,如果您对原始数组的值或新变量的值进行任何更改,另一个也会改变:

js
const fruits = ["Strawberry", "Mango"];
const fruitsAlias = fruits;
// 'fruits' and 'fruitsAlias' are the same object, strictly equivalent.
fruits === fruitsAlias; // true
// Any changes to the 'fruits' array change 'fruitsAlias' too.
fruits.unshift("Apple", "Banana");
console.log(fruits);
// ['Apple', 'Banana', 'Strawberry', 'Mango']
console.log(fruitsAlias);
// ['Apple', 'Banana', 'Strawberry', 'Mango']

创建二维数组

以下示例创建一个作为字符串二维数组的棋盘。第一步是通过将 board[6][4] 中的 'p' 复制到 board[4][4] 来完成的。[6][4] 处的旧位置被清空。

js
const board = [
  ["R", "N", "B", "Q", "K", "B", "N", "R"],
  ["P", "P", "P", "P", "P", "P", "P", "P"],
  [" ", " ", " ", " ", " ", " ", " ", " "],
  [" ", " ", " ", " ", " ", " ", " ", " "],
  [" ", " ", " ", " ", " ", " ", " ", " "],
  [" ", " ", " ", " ", " ", " ", " ", " "],
  ["p", "p", "p", "p", "p", "p", "p", "p"],
  ["r", "n", "b", "q", "k", "b", "n", "r"],
];

console.log(`${board.join("\n")}\n\n`);

// Move King's Pawn forward 2
board[4][4] = board[6][4];
board[6][4] = " ";
console.log(board.join("\n"));

这是输出:

R,N,B,Q,K,B,N,R
P,P,P,P,P,P,P,P
 , , , , , , ,
 , , , , , , ,
 , , , , , , ,
 , , , , , , ,
p,p,p,p,p,p,p,p
r,n,b,q,k,b,n,r

R,N,B,Q,K,B,N,R
P,P,P,P,P,P,P,P
 , , , , , , ,
 , , , , , , ,
 , , , ,p, , ,
 , , , , , , ,
p,p,p,p, ,p,p,p
r,n,b,q,k,b,n,r

使用数组制表一组值

js
const values = [];
for (let x = 0; x < 10; x++) {
  values.push([2 ** x, 2 * x ** 2]);
}
console.table(values);

结果为:

// The first column is the index
0  1    0
1  2    2
2  4    8
3  8    18
4  16   32
5  32   50
6  64   72
7  128  98
8  256  128
9  512  162

使用匹配结果创建数组

RegExp 与字符串之间的匹配结果可以创建一个 JavaScript 数组,该数组具有提供匹配信息的属性和元素。此类数组由 RegExp.prototype.exec()String.prototype.match() 返回。

例如

js
// Match one d followed by one or more b's followed by one d
// Remember matched b's and the following d
// Ignore case

const myRe = /d(b+)(d)/i;
const execResult = myRe.exec("cdbBdbsbz");

console.log(execResult.input); // 'cdbBdbsbz'
console.log(execResult.index); // 1
console.log(execResult); // [ "dbBd", "bB", "d" ]

有关匹配结果的更多信息,请参阅 RegExp.prototype.exec()String.prototype.match() 页面。

迭代方法中改变初始数组

迭代方法不会改变调用它的数组,但作为 callbackFn 提供的函数可以。要记住的关键原则是,只有 0 到 arrayLength - 1 之间的索引会被访问,其中 arrayLength 是首次调用数组方法时数组的长度,但传递给回调的元素是访问索引时的值。因此:

  • callbackFn 不会访问在迭代方法调用开始时超出数组初始长度的任何元素。
  • 对已访问索引的更改不会导致 callbackFn 再次在其上调用。
  • 如果数组中现有但尚未访问的元素被 callbackFn 更改,则传递给 callbackFn 的值将是该元素被访问时的值。已删除的元素不会被访问。

警告:上述并发修改通常会导致难以理解的代码,通常应避免(特殊情况除外)。

以下示例使用 forEach 方法作为示例,但其他按升序访问索引的方法也以相同的方式工作。我们首先定义一个辅助函数:

js
function testSideEffect(effect) {
  const arr = ["e1", "e2", "e3", "e4"];
  arr.forEach((elem, index, arr) => {
    console.log(`array: [${arr.join(", ")}], index: ${index}, elem: ${elem}`);
    effect(arr, index);
  });
  console.log(`Final array: [${arr.join(", ")}]`);
}

对尚未访问的索引的修改将在索引到达时可见:

js
testSideEffect((arr, index) => {
  if (index + 1 < arr.length) arr[index + 1] += "*";
});
// array: [e1, e2, e3, e4], index: 0, elem: e1
// array: [e1, e2*, e3, e4], index: 1, elem: e2*
// array: [e1, e2*, e3*, e4], index: 2, elem: e3*
// array: [e1, e2*, e3*, e4*], index: 3, elem: e4*
// Final array: [e1, e2*, e3*, e4*]

对已访问索引的修改不会改变迭代行为,尽管之后数组会有所不同:

js
testSideEffect((arr, index) => {
  if (index > 0) arr[index - 1] += "*";
});
// array: [e1, e2, e3, e4], index: 0, elem: e1
// array: [e1, e2, e3, e4], index: 1, elem: e2
// array: [e1*, e2, e3, e4], index: 2, elem: e3
// array: [e1*, e2*, e3, e4], index: 3, elem: e4
// Final array: [e1*, e2*, e3*, e4]

在小于初始数组长度的未访问索引处插入 n 个元素将使其被访问。原始数组中现在索引大于初始数组长度的最后 n 个元素将不会被访问:

js
testSideEffect((arr, index) => {
  if (index === 1) arr.splice(2, 0, "new");
});
// array: [e1, e2, e3, e4], index: 0, elem: e1
// array: [e1, e2, e3, e4], index: 1, elem: e2
// array: [e1, e2, new, e3, e4], index: 2, elem: new
// array: [e1, e2, new, e3, e4], index: 3, elem: e3
// Final array: [e1, e2, new, e3, e4]
// e4 is not visited because it now has index 4

在大于初始数组长度的索引处插入 n 个元素不会使其被访问:

js
testSideEffect((arr) => arr.push("new"));
// array: [e1, e2, e3, e4], index: 0, elem: e1
// array: [e1, e2, e3, e4, new], index: 1, elem: e2
// array: [e1, e2, e3, e4, new, new], index: 2, elem: e3
// array: [e1, e2, e3, e4, new, new, new], index: 3, elem: e4
// Final array: [e1, e2, e3, e4, new, new, new, new]

在已访问索引处插入 n 个元素不会使其被访问,但它会将剩余元素向后移动 n 个,因此当前索引及其之前的 n - 1 个元素将再次被访问:

js
testSideEffect((arr, index) => arr.splice(index, 0, "new"));
// array: [e1, e2, e3, e4], index: 0, elem: e1
// array: [new, e1, e2, e3, e4], index: 1, elem: e1
// array: [new, new, e1, e2, e3, e4], index: 2, elem: e1
// array: [new, new, new, e1, e2, e3, e4], index: 3, elem: e1
// Final array: [new, new, new, new, e1, e2, e3, e4]
// e1 keeps getting visited because it keeps getting shifted back

删除未访问索引处的 n 个元素将使其不再被访问。由于数组已缩小,最后 n 次迭代将访问越界索引。如果该方法忽略不存在的索引(参见数组方法和空槽位),最后 n 次迭代将被跳过;否则,它们将收到 undefined

js
testSideEffect((arr, index) => {
  if (index === 1) arr.splice(2, 1);
});
// array: [e1, e2, e3, e4], index: 0, elem: e1
// array: [e1, e2, e3, e4], index: 1, elem: e2
// array: [e1, e2, e4], index: 2, elem: e4
// Final array: [e1, e2, e4]
// Does not visit index 3 because it's out-of-bounds

// Compare this with find(), which treats nonexistent indexes as undefined:
const arr2 = ["e1", "e2", "e3", "e4"];
arr2.find((elem, index, arr) => {
  console.log(`array: [${arr.join(", ")}], index: ${index}, elem: ${elem}`);
  if (index === 1) arr.splice(2, 1);
  return false;
});
// array: [e1, e2, e3, e4], index: 0, elem: e1
// array: [e1, e2, e3, e4], index: 1, elem: e2
// array: [e1, e2, e4], index: 2, elem: e4
// array: [e1, e2, e4], index: 3, elem: undefined

删除已访问索引处的 n 个元素不会改变它们在被删除之前已被访问的事实。由于数组已缩小,当前索引之后的下一个 n 个元素将被跳过。如果该方法忽略不存在的索引,最后 n 次迭代将被跳过;否则,它们将收到 undefined

js
testSideEffect((arr, index) => arr.splice(index, 1));
// array: [e1, e2, e3, e4], index: 0, elem: e1
// Does not visit e2 because e2 now has index 0, which has already been visited
// array: [e2, e3, e4], index: 1, elem: e3
// Does not visit e4 because e4 now has index 1, which has already been visited
// Final array: [e2, e4]
// Index 2 is out-of-bounds, so it's not visited

// Compare this with find(), which treats nonexistent indexes as undefined:
const arr2 = ["e1", "e2", "e3", "e4"];
arr2.find((elem, index, arr) => {
  console.log(`array: [${arr.join(", ")}], index: ${index}, elem: ${elem}`);
  arr.splice(index, 1);
  return false;
});
// array: [e1, e2, e3, e4], index: 0, elem: e1
// array: [e2, e3, e4], index: 1, elem: e3
// array: [e2, e4], index: 2, elem: undefined
// array: [e2, e4], index: 3, elem: undefined

对于按索引降序迭代的方法,插入会导致元素被跳过,删除会导致元素被多次访问。请自行调整上面的代码以查看效果。

规范

规范
ECMAScript® 2026 语言规范
# sec-array-objects

浏览器兼容性

另见