数组

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

¥The Array object, as with arrays in other programming languages, enables storing a collection of multiple items under a single variable name, and has members for performing common array operations.

描述

¥Description

在 JavaScript 中,数组不是 primitives,而是 Array 对象,具有以下核心特性:

¥In JavaScript, arrays aren't primitives but are instead Array objects with the following core characteristics:

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

数组索引

¥Array indices

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

¥Array objects cannot use arbitrary strings as element indexes (as in an associative array) but must use nonnegative integers (or their respective string form). Setting or accessing via non-integers will not set or retrieve an element from the array list itself, but will set or access a variable associated with that array's object property collection. The array's object properties and list of array elements are separate, and the array's traversal and mutation operations cannot be applied to these named properties.

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

¥Array elements are object properties in the same way that toString is a property (to be specific, however, toString() is a method). Nevertheless, trying to access an element of an array as follows throws a syntax error because the property name is not valid:

js
arr.0; // a syntax error

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

¥JavaScript syntax requires properties beginning with a digit to be accessed using bracket notation instead of dot notation. It's also possible to quote the array indices (e.g., years['2'] instead of years[2]), although usually not necessary.

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

¥The 2 in years[2] is coerced into a string by the JavaScript engine through an implicit toString conversion. As a result, '2' and '02' would refer to two different slots on the years object, and the following example could be true:

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

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

¥Only years['2'] is an actual array index. years['02'] is an arbitrary string property that will not be visited in array iteration.

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

¥Relationship between length and numerical properties

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

¥A JavaScript array's length property and numerical properties are connected.

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

¥Several of the built-in array methods (e.g., join(), slice(), indexOf(), etc.) take into account the value of an array's length property when they're called.

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

¥Other methods (e.g., push(), splice(), etc.) also result in updates to an array's length property.

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

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

¥When setting a property on a JavaScript array when the property is a valid array index and that index is outside the current bounds of the array, the engine will update the array's length property accordingly:

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

增加 length 通过添加空槽来扩展数组,而不创建任何新元素 - 甚至不创建任何新元素。

¥Increasing the length extends the array by adding empty slots without creating any new elements — not even 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 属性确实会删除元素。

¥Decreasing the length property does, however, delete elements.

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

length 页对此进行了进一步解释。

¥This is explained further on the length page.

数组方法和空槽

¥Array methods and empty slots

当遇到 稀疏数组 中的空槽时,数组方法有不同的行为。一般来说,较旧的方法(例如 forEach)对空槽的处理方式与包含 undefined 的索引不同。

¥Array methods have different behaviors when encountering empty slots in sparse arrays. In general, older methods (e.g. forEach) treat empty slots differently from indices that contain undefined.

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

¥Methods that have special treatment for empty slots include the following: concat(), copyWithin(), every(), filter(), flat(), flatMap(), forEach(), indexOf(), lastIndexOf(), map(), reduce(), reduceRight(), reverse(), slice(), some(), sort(), and splice(). Iteration methods such as forEach don't visit empty slots at all. Other methods, such as concat, copyWithin, etc., preserve empty slots when doing the copying, so in the end the array is still sparse.

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()

¥Newer methods (e.g. keys) do not treat empty slots specially and treat them as if they contain undefined. Methods that conflate empty slots with undefined elements include the following: entries(), fill(), find(), findIndex(), findLast(), findLastIndex(), includes(), join(), keys(), toLocaleString(), toReversed(), toSorted(), toSpliced(), values(), and 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']

复制方法和变异方法

¥Copying methods and mutating methods

有些方法不会改变调用该方法的现有数组,而是返回一个新数组。他们首先构造一个新数组,然后用元素填充它。复制总是发生 shallowly — 该方法不会复制最初创建的数组之外的任何内容。原始数组的元素被复制到新数组中,如下所示:

¥Some methods do not mutate the existing array that the method was called on, but instead return a new array. They do so by first constructing a new array and then populating it with elements. The copy always happens shallowly — the method never copies anything beyond the initially created array. Elements of the original array(s) are copied into the new array as follows:

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

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

¥Other methods mutate the array that the method was called on, in which case their return value differs depending on the method: sometimes a reference to the same array, sometimes the length of the new array.

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

¥The following methods create new arrays by accessing this.constructor[Symbol.species] to determine the constructor to use: concat(), filter(), flat(), flatMap(), map(), slice(), and splice() (to construct the array of removed elements that's returned).

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

¥The following methods always create new arrays with the Array base constructor: toReversed(), toSorted(), toSpliced(), and with().

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

¥The following table lists the methods that mutate the original array, and the corresponding non-mutating alternative:

变异方法 非变异替代方案
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() 创建副本:

¥An easy way to change a mutating method into a non-mutating alternative is to use the spread syntax or slice() to create a copy first:

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

迭代方法

¥Iterative methods

许多数组方法采用回调函数作为参数。对于数组中的每个元素顺序调用回调函数,最多调用一次,回调函数的返回值用于确定方法的返回值。他们都有相同的签名:

¥Many array methods take a callback function as an argument. The callback function is called sequentially and at most once for each element in the array, and the return value of the callback function is used to determine the return value of the method. They all share the same signature:

js
method(callbackFn, thisArg)

其中 callbackFn 采用三个参数:

¥Where callbackFn takes three arguments:

element

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

index

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

array

调用该方法的数组。

callbackFn 预期返回的内容取决于调用的数组方法。

¥What callbackFn is expected to return depends on the array method that was called.

调用 callbackFn 时,thisArg 参数(默认为 undefined)将用作 this 值。callbackFn 最终可观测到的 this 值根据 通常的规则 确定:如果 callbackFnnon-strict,则原始 this 值被封装到对象中,并且 undefined/null 被替换为 globalThisthisArg 参数与使用 箭头函数 定义的任何 callbackFn 无关,因为箭头函数没有自己的 this binding

¥The thisArg argument (defaults to undefined) will be used as the this value when calling callbackFn. The this value ultimately observable by callbackFn is determined according to the usual rules: if callbackFn is non-strict, primitive this values are wrapped into objects, and undefined/null is substituted with globalThis. The thisArg argument is irrelevant for any callbackFn defined with an arrow function, as arrow functions don't have their own this binding.

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

¥The array argument passed to callbackFn is most useful if you want to read another index during iteration, because you may not always have an existing variable that refers to the current array. You should generally not mutate the array during iteration (see mutating initial array in iterative methods), but you can also use this argument to do so. The array argument is not the array that is being built, in the case of methods like map(), filter(), and flatMap() — there is no way to access the array being built from the callback function.

所有迭代方法都是 copyinggeneric,尽管它们的行为与 空槽 不同。

¥All iterative methods are copying and generic, although they behave differently with empty slots.

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

¥The following methods are iterative:every(), filter(), find(), findIndex(), findLast(), findLastIndex(), flatMap(), forEach(), map(), and some().

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

¥In particular, every(), find(), findIndex(), findLast(), findLastIndex(), and some() do not always invoke callbackFn on every element — they stop iteration as soon as the return value is determined.

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

¥The reduce() and reduceRight() methods also take a callback function and run it at most once for each element in the array, but they have slightly different signatures from typical iterative methods (for example, they don't accept thisArg).

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

¥The sort() method also takes a callback function, but it is not an iterative method. It mutates the array in-place, doesn't accept thisArg, and may invoke the callback multiple times on an index.

迭代方法像下面这样迭代数组(省略了很多技术细节):

¥Iterative methods iterate the array like the following (with a lot of technical details omitted):

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
    }
  }
}

请注意以下事项:

¥Note the following:

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

通用数组方法

¥Generic array methods

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

¥Array methods are always generic — they don't access any internal data of the array object. They only access the array elements through the length property and the indexed elements. This means that they can be called on array-like objects as well.

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

长度属性的标准化

¥Normalization of the length property

length 属性是 转换为整数,然后限制在 0 到 253 之间的范围 - 1.

¥The length property is converted to an integer and then clamped to the range between 0 and 253 - 1. NaN becomes 0, so even when length is not present or is undefined, it behaves as if it has value 0.

该语言避免将 length 设置为 不安全整数。如果 length 设置为大于 253 的数字,所有内置方法都会抛出 TypeError - 1.1,除非在非数组对象上调用该方法,否则通常不会达到安全整数阈值。

¥The language avoids setting length to an unsafe integer. All built-in methods will throw a TypeError if length will be set to a number greater than 253 - 1. However, because the length property of arrays throws an error if it's set to greater than 232 - 1, the safe integer threshold is usually not reached unless the method is called on a non-array object.

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

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

¥Some array methods set the length property of the array object. They always set the value after normalization, so length always ends as an integer.

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

类似数组的对象

¥Array-like objects

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

¥The term array-like object refers to any object that doesn't throw during the length conversion process described above. In practice, such object is expected to actually have a length property and to have indexed elements in the range 0 to length - 1. (If it doesn't have all indices, it will be functionally equivalent to a sparse array.) Any integer index less than zero or greater than length - 1 is ignored when an array method operates on an array-like object.

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

¥Many DOM objects are array-like — for example, NodeList and HTMLCollection. The arguments object is also array-like. You can call array methods on them even if they don't have these methods themselves.

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

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

构造函数

¥Constructor

Array()

创建一个新的 Array 对象。

静态属性

¥Static properties

Array[@@species]

返回 Array 构造函数。

静态方法

¥Static methods

Array.from()

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

Array.fromAsync()

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

Array.isArray()

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

Array.of()

创建一个具有可变数量参数的新 Array 实例,无论参数的数量或类型如何。

实例属性

¥Instance properties

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

¥These properties are defined on Array.prototype and shared by all Array instances.

Array.prototype.constructor

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

Array.prototype[@@unscopables]

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

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

¥These properties are own properties of each Array instance.

length

反映数组中元素的数量。

实例方法

¥Instance methods

Array.prototype.at()

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

Array.prototype.concat()

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

Array.prototype.copyWithin()

复制数组中的一系列数组元素。

Array.prototype.entries()

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

Array.prototype.every()

如果调用数组中的每个元素都满足测试函数,则返回 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

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[@@iterator]()

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

示例

¥Examples

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

¥This section provides some examples of common array operations in JavaScript.

注意:如果你还不熟悉数组基础知识,请考虑首先阅读 JavaScript 第一步:数组解释什么是数组,并包括常见数组操作的其他示例。

¥Note: If you're not yet familiar with array basics, consider first reading JavaScript First Steps: Arrays, which explains what arrays are, and includes other examples of common array operations.

创建一个数组

¥Create an array

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

¥This example shows three ways to create new array: first using array literal notation, then using the Array() constructor, and finally using String.prototype.split() to build the array from a string.

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

从数组创建字符串

¥Create a string from an array

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

¥This example uses the join() method to create a string from the fruits array.

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

通过索引访问数组项

¥Access an array item by its index

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

¥This example shows how to access items in the fruits array by specifying the index number of their position in the array.

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

查找数组中某个项目的索引

¥Find the index of an item in an array

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

¥This example uses the indexOf() method to find the position (index) of the string "Banana" in the fruits array.

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

检查数组是否包含某个项目

¥Check if an array contains a certain item

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

¥This example shows two ways to check if the fruits array contains "Banana" and "Cherry": first with the includes() method, and then with the indexOf() method to test for an index value that's not -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

将一个项目追加到数组中

¥Append an item to an array

此示例使用 push() 方法将新字符串附加到 fruits 数组。

¥This example uses the push() method to append a new string to the fruits array.

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

从数组中删除最后一项

¥Remove the last item from an array

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

¥This example uses the pop() method to remove the last item from the fruits array.

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

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

¥Note: pop() can only be used to remove the last item from an array. To remove multiple items from the end of an array, see the next example.

从数组末尾删除多个项目

¥Remove multiple items from the end of an array

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

¥This example uses the splice() method to remove the last 3 items from the fruits array.

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 个项目

¥Truncate an array down to just its first N items

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

¥This example uses the splice() method to truncate the fruits array down to just its first 2 items.

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"]

从数组中删除第一项

¥Remove the first item from an array

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

¥This example uses the shift() method to remove the first item from the fruits array.

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

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

¥Note: shift() can only be used to remove the first item from an array. To remove multiple items from the beginning of an array, see the next example.

从数组开头删除多个项目

¥Remove multiple items from the beginning of an array

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

¥This example uses the splice() method to remove the first 3 items from the fruits array.

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"]

将新的第一项添加到数组中

¥Add a new first item to an array

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

¥This example uses the unshift() method to add, at index 0, a new item to the fruits array — making it the new first item in the array.

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

按索引删除单个项目

¥Remove a single item by index

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

¥This example uses the splice() method to remove the string "Banana" from the fruits array — by specifying the index position of "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"]

按索引删除多个项目

¥Remove multiple items by index

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

¥This example uses the splice() method to remove the strings "Banana" and "Strawberry" from the fruits array — by specifying the index position of "Banana", along with a count of the number of total items to remove.

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"]

替换数组中的多个项目

¥Replace multiple items in an array

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

¥This example uses the splice() method to replace the last 2 items in the fruits array with new items.

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"]

迭代数组

¥Iterate over an array

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

¥This example uses a for...of loop to iterate over the fruits array, logging each item to the console.

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() 方法的示例。

¥But for...of is just one of many ways to iterate over any array; for more ways, see Loops and iteration, and see the documentation for the every(), filter(), flatMap(), map(), reduce(), and reduceRight() methods — and see the next example, which uses the forEach() method.

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

¥Call a function on each element in an array

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

¥This example uses the forEach() method to call a function on each element in the fruits array; the function causes each item to be logged to the console, along with the item's index number.

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

将多个数组合并在一起

¥Merge multiple arrays together

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

¥This example uses the concat() method to merge the fruits array with a moreFruits array, to produce a new combinedFruits array. Notice that fruits and moreFruits remain unchanged.

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"]

复制数组

¥Copy an array

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

¥This example shows three ways to create a new array from the existing fruits array: first by using spread syntax, then by using the from() method, and then by using the slice() method.

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() 将字符串转换回完全独立于原始数组的新数组。

¥All built-in array-copy operations (spread syntax, Array.from(), Array.prototype.slice(), and Array.prototype.concat()) create shallow copies. If you instead want a deep copy of an array, you can use JSON.stringify() to convert the array to a JSON string, and then JSON.parse() to convert the string back into a new array that's completely independent from the original array.

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

你还可以使用 structuredClone() 方法创建深层副本,该方法的优点是允许将源中的 可转让物品 转移到新副本,而不仅仅是克隆。

¥You can also create deep copies using the structuredClone() method, which has the advantage of allowing transferable objects in the source to be transferred to the new copy, rather than just cloned.

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

¥Finally, it's important to understand that assigning an existing array to a new variable doesn't create a copy of either the array or its elements. Instead the new variable is just a reference, or alias, to the original array; that is, the original array's name and the new variable name are just two names for the exact same object (and so will always evaluate as strictly equivalent). Therefore, if you make any changes at all either to the value of the original array or to the value of the new variable, the other will change, too:

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']

创建二维数组

¥Creating a two-dimensional array

下面创建一个棋盘作为二维字符串数组。第一步是将 board[6][4] 中的 'p' 复制到 board[4][4][6][4] 处的旧位置被留空。

¥The following creates a chessboard as a two-dimensional array of strings. The first move is made by copying the 'p' in board[6][4] to board[4][4]. The old position at [6][4] is made blank.

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"));

这是输出:

¥Here is the output:

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

使用数组将一组值制成表格

¥Using an array to tabulate a set of values

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

结果是

¥Results in

// 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

使用匹配结果创建数组

¥Creating an array using the result of a match

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

¥The result of a match between a RegExp and a string can create a JavaScript array that has properties and elements which provide information about the match. Such an array is returned by RegExp.prototype.exec() and String.prototype.match().

例如:

¥For example:

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() 页面。

¥For more information about the result of a match, see the RegExp.prototype.exec() and String.prototype.match() pages.

在迭代方法中改变初始数组

¥Mutating initial array in iterative methods

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

¥Iterative methods do not mutate the array on which it is called, but the function provided as callbackFn can. The key principle to remember is that only indexes between 0 and arrayLength - 1 are visited, where arrayLength is the length of the array at the time the array method was first called, but the element passed to the callback is the value at the time the index is visited. Therefore:

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

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

¥Warning: Concurrent modifications of the kind described above frequently lead to hard-to-understand code and are generally to be avoided (except in special cases).

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

¥The following examples use the forEach method as an example, but other methods that visit indexes in ascending order work in the same way. We will first define a helper function:

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(", ")}]`);
}

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

¥Modification to indexes not visited yet will be visible once the index is reached:

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*]

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

¥Modification to already visited indexes does not change iteration behavior, although the array will be different afterwards:

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 个元素将不会被访问:

¥Inserting n elements at unvisited indexes that are less than the initial array length will make them be visited. The last n elements in the original array that now have index greater than the initial array length will not be visited:

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 个元素不会使它们被访问:

¥Inserting n elements with index greater than the initial array length will not make them be visited:

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 个元素:

¥Inserting n elements at already visited indexes will not make them be visited, but it shifts remaining elements back by n, so the current index and the n - 1 elements before it are visited again:

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

¥Deleting n elements at unvisited indexes will make them not be visited anymore. Because the array has shrunk, the last n iterations will visit out-of-bounds indexes. If the method ignores non-existent indexes (see array methods and empty slots), the last n iterations will be skipped; otherwise, they will receive 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

¥Deleting n elements at already visited indexes does not change the fact that they were visited before they get deleted. Because the array has shrunk, the next n elements after the current index are skipped. If the method ignores non-existent indexes, the last n iterations will be skipped; otherwise, they will receive 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

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

¥For methods that iterate in descending order of index, insertion causes elements to be skipped, and deletion causes elements to be visited multiple times. Adjust the code above yourself to see the effects.

规范

Specification
ECMAScript Language Specification
# sec-array-objects

¥Specifications

浏览器兼容性

BCD tables only load in the browser

¥Browser compatibility

也可以看看