数组
基线 广泛可用
此功能已得到很好的建立,并且适用于许多设备和浏览器版本。它自 2015 年 7 月.
报告反馈
描述
与其他编程语言中的数组一样,Array
对象允许在单个变量名下存储多个项目的集合,并拥有用于执行常用数组操作的成员。
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
,根本不会访问空槽。其他方法,例如 concat
、copyWithin
等,在复制时会保留空槽,因此最终数组仍然是稀疏的。
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']
复制方法和修改方法
某些方法不会修改调用该方法的现有数组,而是返回一个新数组。它们通过首先构造一个新数组,然后用元素填充它来做到这一点。复制总是 浅复制 — 该方法永远不会复制超出最初创建的数组的任何内容。原始数组的元素以如下方式复制到新数组中
- 对象:对象引用被复制到新数组中。原始数组和新数组都引用同一个对象。也就是说,如果引用的对象被修改,则更改对新数组和原始数组都是可见的。
- 原始类型,例如字符串、数字和布尔值(不是
String
、Number
和Boolean
对象):它们的值被复制到新数组中。
其他方法修改调用该方法的数组,在这种情况下,它们的返回值根据方法的不同而不同:有时是相同数组的引用,有时是新数组的长度。
以下方法通过访问 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
接受三个参数
callbackFn
预计返回什么取决于被调用的数组方法。
thisArg
参数(默认为 undefined
)将用作调用 callbackFn
时的 this
值。callbackFn
最终可观察到的 this
值是根据 通常的规则 确定的:如果 callbackFn
是 非严格的,则原始 this
值将被包装到对象中,而 undefined
/null
将被替换为 globalThis
。对于使用 箭头函数 定义的任何 callbackFn
,thisArg
参数都无关紧要,因为箭头函数没有自己的 this
绑定。
传递给 callbackFn
的 array
参数在您想在迭代期间读取另一个索引时非常有用,因为您可能并不总是拥有一个引用当前数组的现有变量。您通常不应该在迭代期间修改数组(请参阅 在迭代方法中修改初始数组),但您也可以使用此参数来进行修改。对于像 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
}
}
}
请注意以下几点
- 并非所有方法都执行
i in this
测试。find
、findIndex
、findLast
和findLastIndex
方法不会执行,但其他方法会执行。 length
在循环开始之前被记忆。这会影响迭代期间插入和删除的处理方式(请参阅 在迭代方法中修改初始数组)。- 该方法不会记忆数组内容,因此如果在迭代期间修改了任何索引,则可能会观察到新值。
- 上面的代码按索引的升序迭代数组。一些方法按索引的降序迭代(
for (let i = length - 1; i >= 0; i--)
):reduceRight()
、findLast()
和findLastIndex()
。 reduce
和reduceRight
的签名略有不同,并且不总是从第一个/最后一个元素开始。
泛型数组方法
数组方法始终是泛型的 — 它们不访问数组对象的任何内部数据。它们仅通过 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
属性,并在0
到length - 1
范围内具有索引元素。(如果它没有所有索引,它将在功能上等效于稀疏数组。)任何小于零或大于length - 1
的整数索引在数组方法对类数组对象进行操作时都会被忽略。
许多 DOM 对象是类数组的 - 例如,NodeList
和HTMLCollection
。 arguments
对象也是类数组的。即使它们本身没有这些方法,您也可以在它们上调用数组方法。
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()
-
确定调用数组是否包含一个值,根据情况返回
true
或false
。 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
数组。请注意,fruits
和 moreFruits
保持不变。
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 表仅在浏览器中加载