数组

基线 广泛可用

此功能已得到很好的建立,并且适用于许多设备和浏览器版本。它自 2015 年 7 月.

报告反馈

描述

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

  • 在 JavaScript 中,数组不是基本类型,而是Array 对象,具有以下核心特征
  • JavaScript 数组是可调整大小的,并且可以包含不同数据类型的混合。(当这些特征不可取时,请改用类型化数组。)
  • JavaScript 数组不是关联数组,因此,数组元素不能使用任意字符串作为索引访问,而必须使用非负整数(或其相应的字符串形式)作为索引访问。
  • JavaScript 数组是从零开始索引的:数组的第一个元素位于索引0处,第二个元素位于索引1处,依此类推——最后一个元素位于数组的length 属性值减去1的位置。

JavaScript数组复制操作创建浅层副本。(所有标准内置的任何 JavaScript 对象的复制操作都会创建浅层副本,而不是深层副本。)

数组索引

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

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
arr.0; // a syntax error

js

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

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
console.log(years["2"] !== years["02"]);

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

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

长度与数值属性之间的关系

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

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

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
const fruits = [];
fruits.push("banana", "apple", "peach");
console.log(fruits.length); // 3

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

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
fruits[5] = "mango";
console.log(fruits[5]); // 'mango'
console.log(Object.keys(fruits)); // ['0', '1', '2', '5']
console.log(fruits.length); // 6

当设置 JavaScript 数组上的属性时,如果该属性是一个有效的数组索引,并且该索引超出了数组的当前边界,则引擎会相应地更新数组的length 属性

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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 会通过添加空插槽来扩展数组,但不会创建任何新元素——甚至不会创建undefined

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
fruits.length = 2;
console.log(Object.keys(fruits)); // ['0', '1']
console.log(fruits.length); // 2

但是,减少length 属性会删除元素。

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

数组方法和空插槽

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

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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()

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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() 首先创建副本

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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

迭代方法

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

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
method(callbackFn, thisArg)

其中 callbackFn 接受三个参数

element

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

index

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

array

调用该方法的数组。

callbackFn 预计返回什么取决于被调用的数组方法。

thisArg 参数(默认为 undefined)将用作调用 callbackFn 时的 this 值。callbackFn 最终可观察到的 this 值是根据 通常的规则 确定的:如果 callbackFn非严格的,则原始 this 值将被包装到对象中,而 undefined/null 将被替换为 globalThis。对于使用 箭头函数 定义的任何 callbackFnthisArg 参数都无关紧要,因为箭头函数没有自己的 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,并且可能多次对一个索引调用回调函数。

迭代方法迭代数组,类似于以下方式(省略了许多技术细节)

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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 属性和索引元素访问数组元素。这意味着它们也可以在类数组对象上调用。

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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,就会抛出错误,因此除非在非数组对象上调用该方法,否则通常不会达到安全整数阈值。

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
Array.prototype.flat.call({}); // []

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

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
const a = { length: 0.7 };
Array.prototype.push.call(a);
console.log(a.length); // 0

类数组对象

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

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

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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()

如果调用数组中的每个元素都满足测试函数,则返回true

Array.prototype.fill()

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

Array.prototype.filter()

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

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

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()从字符串构建数组。

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
// '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数组创建字符串。

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
const fruits = ["Apple", "Banana"];
const fruitsString = fruits.join(", ");
console.log(fruitsString);
// "Apple, Banana"

通过索引访问数组项

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

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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数组中的位置(索引)。

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
const fruits = ["Apple", "Banana"];
console.log(fruits.indexOf("Banana"));
// 1

检查数组是否包含某个特定项

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

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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数组。

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
const fruits = ["Apple", "Banana"];
const newLength = fruits.push("Orange");
console.log(fruits);
// ["Apple", "Banana", "Orange"]
console.log(newLength);
// 3

从数组中删除最后一项

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

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
const fruits = ["Apple", "Banana", "Orange"];
const removedItem = fruits.pop();
console.log(fruits);
// ["Apple", "Banana"]
console.log(removedItem);
// Orange

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

从数组末尾删除多个项

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

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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 项。

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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数组中删除第一项。

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
const fruits = ["Apple", "Banana"];
const removedItem = fruits.shift();
console.log(fruits);
// ["Banana"]
console.log(removedItem);
// Apple

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

从数组开头删除多个项

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

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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()方法在索引0处添加一个新项到fruits数组中 - 使其成为数组中新的第一项。

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
const fruits = ["Banana", "Mango"];
const newLength = fruits.unshift("Strawberry");
console.log(fruits);
// ["Strawberry", "Banana", "Mango"]
console.log(newLength);
// 3

通过索引删除单个项

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

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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"的索引位置,以及要删除的总项数。

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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 项。

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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 数组,并将每个元素记录到控制台。

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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 数组中的每个元素调用函数;该函数将每个元素与其索引号一起记录到控制台。

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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 保持不变。

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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() 方法。

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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() 将字符串转换回与原始数组完全独立的新数组。

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
const fruitsDeepCopy = JSON.parse(JSON.stringify(fruits));

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

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

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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] 处的旧位置变为空白。

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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

使用数组制表一组值

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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() 返回。

例如

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
// 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 方法作为示例,但其他按升序访问索引的方法的工作方式相同。我们将首先定义一个辅助函数

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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(", ")}]`);
}

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

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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*]

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

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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* 个元素将不会被访问

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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* 个元素将不会使它们被访问

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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* 个元素将再次被访问

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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

数组元素与toString 属性的方式相同(具体来说,toString() 是一个方法)。但是,尝试按如下方式访问数组的元素会抛出语法错误,因为属性名称无效
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 语言规范
# sec-array-objects

浏览器兼容性

BCD 表仅在浏览器中加载

另请参阅