GeezerVison is showing developer.mozilla.org — Array - JavaScript | MDN
Array
基线
广泛可用
*
自 2015年7月 起,此特性已在主流浏览器中得到支持,可在大多数设备和浏览器版本中正常使用。
* 此特性的某些部分的支持程度可能有所不同。
在 JavaScript 中,数组不是原始类型,而是具有以下核心特征的 Array 对象:
- JavaScript 数组是可调整大小的,并且可以包含不同的数据类型。(当不需要这些特征时,可以使用类型化数组。)
- JavaScript 数组不是关联数组,因此,不能使用任意字符串作为索引访问数组元素,但必须使用非负整数(或它们各自的字符串形式)作为索引访问。
- JavaScript 数组的索引从 0 开始:数组的第一个元素在索引
0 处,第二个在索引 1 处,以此类推,最后一个元素是数组的 length 属性减去 1 的值。
- JavaScript 数组复制操作创建浅拷贝。(所有 JavaScript 对象的标准内置复制操作都会创建浅拷贝,而不是深拷贝)。
Array 对象不能使用任意字符串作为元素索引(如关联数组),必须使用非负整数(或它们的字符串形式)。通过非整数设置或访问不会设置或从数组列表本身检索元素,但会设置或访问与该数组的对象属性集合相关的变量。数组的对象属性和数组元素列表是分开的,数组的遍历和修改操作不能应用于这些具名属性。
数组元素是对象属性,就像 toString 是属性一样(具体来说,toString() 是一种方法)。然而,尝试按以下方式访问数组的元素会抛出语法错误,因为属性名无效:
JavaScript 语法要求使用方括号表示法而不是点号表示法来访问以数字开头的属性。也可以用引号包裹数组索引(例如,years['2'] 而不是 years[2]),尽管通常没有必要。
JavaScript 引擎通过隐式的 toString,将 years[2] 中的 2 强制转换为字符串。因此,'2' 和 '02' 将指向 years 对象上的两个不同的槽位,下面的例子可能是 true:
js console.log(years["2"] !== years["02"]);
只有 years['2'] 是一个实际的数组索引。years['02'] 是一个在数组迭代中不会被访问的任意字符串属性。
JavaScript 数组的 length 属性和数值属性是连接的。
一些内置数组方法(例如 join()、slice()、indexOf() 等)在被调用时会考虑到数组的 length 属性的值。
其他方法(例如,push()、splice() 等)也会更新数组的 length 属性。
js const fruits = [];
fruits.push("banana", "apple", "peach");
console.log(fruits.length); // 3
当在 JavaScript 数组上设置一个属性时,如果该属性是一个有效的数组索引并且该索引在数组的当前边界之外,引擎将相应地更新数组的 length 属性:
js fruits[5] = "mango";
console.log(fruits[5]); // 'mango'
console.log(Object.keys(fruits)); // ['0', '1', '2', '5']
console.log(fruits.length); // 6
增加 length 会通过添加空槽来扩展数组,而不是创建任何新元素——甚至不会是 undefined。
js fruits.length = 10;
console.log(fruits); // ['banana', 'apple', 'peach', empty x 2, 'mango', empty x 4]
console.log(Object.keys(fruits)); // ['0', '1', '2', '5']
console.log(fruits.length); // 10
console.log(fruits[8]); // undefined
但是,减少 length 属性会删除元素。
js fruits.length = 2;
console.log(Object.keys(fruits)); // ['0', '1']
console.log(fruits.length); // 2
这将在 length 页中进一步解释。
数组方法在遇到空槽时有不同的行为,在稀疏数组中数组方法遇到空槽时有不同的行为。通常,较旧的方法(例如 forEach)处理空槽的方式与处理包含 undefined 索引的方式不同。
对空槽进行特殊处理的方法包括:concat()、copyWithin()、every()、filter()、flat()、flatMap()、forEach()、indexOf()、lastIndexOf()、map()、reduce()、reduceRight()、reverse()、slice()、some()、sort() 和 splice()。诸如 forEach 之类的迭代方法根本不会访问空槽。其他方法,如 concat、copyWithin 等,在进行复制时会保留空槽,因此最终数组依然是稀疏的。
js const colors = ["红", "黄", "蓝"];
colors[5] = "紫";
colors.forEach((item, index) => {
console.log(`${index}:${item}`);
});
// 输出:
// 0:红
// 1:黄
// 2:蓝
// 5:紫
colors.reverse(); // ['紫', 空槽 × 2, '蓝', '黄', '红']
较新的方法(例如 keys)不会对空槽进行特殊处理,而是将它们视为包含 undefined。将空槽合并为 undefined 元素方法有:entries()、fill()、find()、findIndex()、findLast()、findLastIndex()、includes()、join()、keys()、toLocaleString()、values() 和 with()。
js const colors = ["红", "黄", "蓝"];
colors[5] = "紫";
const iterator = colors.keys();
for (const key of iterator) {
console.log(`${key}:${colors[key]}`);
}
// 输出
// 0:红
// 1:黄
// 2:蓝
// 3:undefined
// 4:undefined
// 5:紫
const newColors = colors.toReversed(); // ['紫', undefined, undefined, '蓝', '黄', '红']
许多数组方法接受一个回调函数作为参数。回调函数按顺序为数组中的每个元素调用,且最多调用一次,并且回调函数的返回值用于确定方法的返回值。它们都具有相同的方法签名:
js method(callbackFn, thisArg)
其中 callbackFn 接受三个参数
element
-
数组中当前正在处理的元素。
index
-
正在处理的元素在数组中的索引。
array
-
调用该方法的数组。
callbackFn 的返回值取决于调用的数组方法。
thisArg 参数(默认为 undefined)将在调用 callbackFn 时用作 this 值。最终由 callbackFn 观察到的 this 值根据通常的规则确定:如果 callbackFn 是非严格模式,原始 this 值将被包装为对象,并将 undefined/null 替换为 globalThis。对于使用箭头函数 定义的任何 callbackFn 来说,thisArg 参数都是无关紧要的,因为箭头函数没有自己的 this 绑定。
如果想要在迭代期间读取另一个索引值的话,传递给 callbackFn 的 array 参数是有用的,因为可能并不总是有一个引用当前数据的现有变量。在迭代过程中,通常不应更改数组(参见 迭代方法中的改变初始数组),但可以使用这个参数这样做。array 参数不是正在构建的数组,在类似 map()、filter() 和 flatMap() 方法的情况下——无法通过回调函数访问正在构建的数组。
所有迭代方法都是复制方法和通用方法,尽管它们在处理空槽时的行为不同。
以下方法是迭代方法:every()、filter()、find()、findIndex()、findLast()、findLastIndex()、flatMap()、forEach()、map() 和 some()。
特别地,every()、find()、findIndex()、findLast()、findLastIndex() 和 some() 并不总是在每个元素上调用 callbackFn——它们在确定返回值后立即停止迭代。
reduce() 和 reduceRight() 方法也接受一个回调函数,并对数组中的每个元素最多运行一次,但它们的方法签名与典型的迭代方法略有不同(例如,它们不接受 thisArg)。
sort() 方法也接受一个回调函数,但它不是一个迭代方法。它会就地修改数组,不接受 thisArg,并且可能在索引上多次调用回调函数。
迭代方法迭代数组,如下所示(省略许多技术细节):
js function method(callbackFn, thisArg) {
const length = this.length;
for (let i = 0; i < length; i++) {
if (i in this) {
const result = callbackFn.call(thisArg, this[i], i, this);
// 使用 result 做一些事,也许更早地返回
}
}
}
备注如下内容:
- 不是所有的方法都执行
i in this 验证,find、findIndex、findLast 和 findLastIndex 方法不执行,其他的会。
length 变量在循环开始前存储。这会影响迭代过程中插入和删除的处理方式(参见迭代方法中的改变初始数组)。
method 方法不会存储数组内容,因此如果迭代期间,有任何索引发生更改,可以观察到新的值。
- 上面的代码按索引的升序迭代数组,有一些方法按索引降序迭代(
for (let i = length - 1; i >= 0; i--)):reduceRight()、findLast() 和 findLastIndex()。
reduce 和 reduceRight 具有略微不同的签名,并不总是从第一个/最后一个元素开始。
数组方法总是通用的——它们不访问数组对象的任何内部数据。它们只通过 length 属性和索引访问数组元素。这意味着它们也可以在类数组对象上调用。
js const arrayLike = {
0: "a",
1: "b",
length: 2,
};
console.log(Array.prototype.join.call(arrayLike, "+")); // 'a+b'
长度属性的规范化
length 属性被转换为一个数字,被截断为一个整数,然后固定为 0 到 253 - 1 之间的范围。NaN 变成 0,所以即使 length 没有出现或是 undefined,它也会表现得好像它的值是 0。
JavaScript 避免将 length 设置为不安全的整数。如果 length 将被设置为大于 253 - 1 的数字,则所有内置方法都将抛出 TypeError。但是,由于数组的 length 属性在设置为大于 232 时会抛出错误,因此通常不会达到安全整数阈值,除非该方法在非数组对象上调用。
js Array.prototype.flat.call({}); // []
一些数组方法会设置数组对象的 length 属性。它们总是在规范化后设置值,因此 length 总是以整数结尾。
js const a = { length: 0.7 };
Array.prototype.push.call(a);
console.log(a.length); // 0
类数组对象
术语类数组对象指的是在上面描述的 length 转换过程中不抛出的任何对象。在实践中,这样的对象应该实际具有 length 属性,并且索引元素的范围在 0 到 length - 1 之间。(如果它没有所有的索引,它将在功能上等同于稀疏数组。)
许多 DOM 对象都是类数组对象——例如 NodeList 和 HTMLCollection。arguments 对象也是类数组对象。你可以在它们上调用数组方法,即使它们本身没有这些方法。
js function f() {
console.log(Array.prototype.join.call(arguments, "+"));
}
f("a", "b"); // 'a+b'
本节提供一些 JavaScript 中常见的数组操作示例。
下面的例子展示了三种创建新数组的方法:首先使用数组字面量,然后使用 Array() 构造函数,最后使用 String.prototype.split() 从字符串构建数组。
js // 使用数组字面量创建的 'fruits' 数组。
const fruits = ["Apple", "Banana"];
console.log(fruits.length);
// 2
// 使用 Array() 构建函数创建的 'fruits2' 数组。
const fruits2 = new Array("Apple", "Banana");
console.log(fruits2.length);
// 2
// 使用 String.prototype.split() 方法创建的 'fruits3' 数组。
const fruits3 = "Apple, Banana".split(", ");
console.log(fruits3.length);
// 2
下面的例子使用 join() 方法从 fruits 数组中创建一个字符串。
js const fruits = ["Apple", "Banana"];
const fruitsString = fruits.join(", ");
console.log(fruitsString);
// "Apple, Banana"
下面的例子展示了如何通过指定它们在数组中的位置的索引号来访问 fruits 数组中的元素。
js const fruits = ["Apple", "Banana"];
// 数组第一个元素的索引始终为 0。
fruits[0]; // Apple
// 数组第二个元素的索引始终为 1。
fruits[1]; // Banana
// 数组最后一个元素的索引总是比数组的长度小 1。
fruits[fruits.length - 1]; // Banana
// 使用大于数组长度的索引会返回"undefined"
fruits[99]; // undefined
下面的例子使用 indexOf() 方法查找字符串 "Banana" 在 fruits 数组中的位置(索引)。
js const fruits = ["Apple", "Banana"];
console.log(fruits.indexOf("Banana"));
// 1
下面的例子展示了两种检查 fruits 数组是否包含 "Banana" 和 "Cherry" 的方法:首先使用 includes() 方法,然后使用 indexOf() 方法来测试索引值不是 -1。
js const fruits = ["Apple", "Banana"];
fruits.includes("Banana"); // true
fruits.includes("Cherry"); // false
// 如果 indexOf() 不返回 -1,则数组包含给定的元素。
fruits.indexOf("Banana") !== -1; // true
fruits.indexOf("Cherry") !== -1; // false
下面的例子使用 push() 方法向 fruits 数组追加一个新字符串。
js const fruits = ["Apple", "Banana"];
const newLength = fruits.push("Orange");
console.log(fruits);
// ["Apple", "Banana", "Orange"]
console.log(newLength);
// 3
下面的例子使用 pop() 方法从 fruits 数组中移除最后一个元素。
js const fruits = ["Apple", "Banana", "Orange"];
const removedItem = fruits.pop();
console.log(fruits);
// ["Apple", "Banana"]
console.log(removedItem);
// Orange
备注:pop() 只能用于从数组中移除最后一个元素。若要从数组末尾移除多个元素,请参见下一个示例。
下面的例子使用 splice() 方法从 fruits 数组中移除最后 3 个元素。
js const fruits = ["Apple", "Banana", "Strawberry", "Mango", "Cherry"];
const start = -3;
const removedItems = fruits.splice(start);
console.log(fruits);
// ["Apple", "Banana"]
console.log(removedItems);
// ["Strawberry", "Mango", "Cherry"]
下面的例子使用 splice() 方法将 fruits 数组截断到只剩下前 2 个元素。
js const fruits = ["Apple", "Banana", "Strawberry", "Mango", "Cherry"];
const start = 2;
const removedItems = fruits.splice(start);
console.log(fruits);
// ["Apple", "Banana"]
console.log(removedItems);
// ["Strawberry", "Mango", "Cherry"]
下面的例子使用 shift() 方法从 fruits 数组中移除第一个元素。
js const fruits = ["Apple", "Banana"];
const removedItem = fruits.shift();
console.log(fruits);
// ["Banana"]
console.log(removedItem);
// Apple
备注:shift() 只能用于从数组中移除第一个元素。若要从数组的开头移除多个元素,请参见下一个示例。
下面的例子使用 splice() 方法从 fruits 数组中移除前 3 个元素。
js const fruits = ["Apple", "Strawberry", "Cherry", "Banana", "Mango"];
const start = 0;
const deleteCount = 3;
const removedItems = fruits.splice(start, deleteCount);
console.log(fruits);
// ["Banana", "Mango"]
console.log(removedItems);
// ["Apple", "Strawberry", "Cherry"]
下面的例子使用 unshift() 方法在索引 0 处向 fruits 数组中添加一个新元素——使其成为数组中新的第一元素。
js const fruits = ["Banana", "Mango"];
const newLength = fruits.unshift("Strawberry");
console.log(fruits);
// ["Strawberry", "Banana", "Mango"]
console.log(newLength);
// 3
下面的例子使用 splice() 方法从 fruits 数组中删除字符串 "Banana"——通过指定 "Banana" 的索引位置。
js const fruits = ["Strawberry", "Banana", "Mango"];
const start = fruits.indexOf("Banana");
const deleteCount = 1;
const removedItems = fruits.splice(start, deleteCount);
console.log(fruits);
// ["Strawberry", "Mango"]
console.log(removedItems);
// ["Banana"]
下面的例子使用 splice() 方法从 fruits 数组中删除字符串 "Banana" 和 "Strawberry"——通过指定 "Banana" 的索引位置,以及要移除的元素总数。
js const fruits = ["Apple", "Banana", "Strawberry", "Mango"];
const start = 1;
const deleteCount = 2;
const removedItems = fruits.splice(start, deleteCount);
console.log(fruits);
// ["Apple", "Mango"]
console.log(removedItems);
// ["Banana", "Strawberry"]
下面的例子使用 splice() 方法将 fruits 数组中的最后两个元素替换为新元素。
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"]
下面的例子使用 forEach() 方法在 fruits 数组中的每个元素上调用一个函数;该函数将每个元素以及元素的索引号打印到控制台。
js const fruits = ["Apple", "Mango", "Cherry"];
fruits.forEach((item, index, array) => {
console.log(item, index);
});
// Apple 0
// Mango 1
// Cherry 2
下面的例子使用 concat() 方法将 fruits 数组与 moreFruits 数组合并,生成一个新的 combinedFruits 数组。注意,fruits 和 moreFruits 保持不变。
js const fruits = ["Apple", "Banana", "Strawberry"];
const moreFruits = ["Mango", "Cherry"];
const combinedFruits = fruits.concat(moreFruits);
console.log(combinedFruits);
// ["Apple", "Banana", "Strawberry", "Mango", "Cherry"]
// The 'fruits' array remains unchanged.
console.log(fruits);
// ["Apple", "Banana", "Strawberry"]
// The 'moreFruits' array also remains unchanged.
console.log(moreFruits);
// ["Mango", "Cherry"]
下面的例子展示了从现有的 fruits 数组创建新数组的三种方法:首先使用展开语法,然后使用 from() 方法,然后使用 slice() 方法。
js const fruits = ["Strawberry", "Mango"];
// Create a copy using spread syntax.
const fruitsCopy = [...fruits];
// ["Strawberry", "Mango"]
// Create a copy using the from() method.
const fruitsCopy2 = Array.from(fruits);
// ["Strawberry", "Mango"]
// Create a copy using the slice() method.
const fruitsCopy3 = fruits.slice();
// ["Strawberry", "Mango"]
所有内置的数组复制操作(展开语法、Array.from()、Array.prototype.slice() 和 Array.prototype.concat())都会创建浅拷贝。如果你想要一个数组的深拷贝,你可以使用 JSON.stringify() 将数组转换成一个 JSON 字符串,然后使用 JSON.parse() 将字符串转换回一个完全独立于原数组的新数组。
js const fruitsDeepCopy = JSON.parse(JSON.stringify(fruits));
你还可以使用 structuredClone() 方法创建深拷贝,该方法的优点是允许源代码中的可转移对象被转移到新的副本,而不仅仅是克隆。
最后,重要的是要理解,将现有数组赋值给新变量并不会创建数组或其元素的副本。相反,新变量只是对原数组的引用或别名;也就是说,原来的数组名和新的变量名只是同一个对象的两个名称(因此总是被计算为严格相等)。因此,如果你对原数组的值或新变量的值做了任何改变,另一个也会改变:
js const fruits = ["Strawberry", "Mango"];
const fruitsAlias = fruits;
// 'fruits' 和 'fruitsAlias' 是同一个对象,严格相等。
fruits === fruitsAlias; // true
// 对 'fruits' 数组的任何更改也会更改 'fruitsAlias'。
fruits.unshift("Apple", "Banana");
console.log(fruits);
// ['Apple', 'Banana', 'Strawberry', 'Mango']
console.log(fruitsAlias);
// ['Apple', 'Banana', 'Strawberry', 'Mango']
下面的例子创建了一个代表棋盘的二维字符串数组。第一步是将 board[6][4] 中的 'p' 复制到 board[4][4]。原本的 [6][4] 位置则被设置为空格。
js const board = [
["R", "N", "B", "Q", "K", "B", "N", "R"],
["P", "P", "P", "P", "P", "P", "P", "P"],
[" ", " ", " ", " ", " ", " ", " ", " "],
[" ", " ", " ", " ", " ", " ", " ", " "],
[" ", " ", " ", " ", " ", " ", " ", " "],
[" ", " ", " ", " ", " ", " ", " ", " "],
["p", "p", "p", "p", "p", "p", "p", "p"],
["r", "n", "b", "q", "k", "b", "n", "r"],
];
console.log(`${board.join("\n")}\n\n`);
// 将国王的兵向前移动 2
board[4][4] = board[6][4];
board[6][4] = " ";
console.log(board.join("\n"));
下面是输出:
R,N,B,Q,K,B,N,R
P,P,P,P,P,P,P,P
, , , , , , ,
, , , , , , ,
, , , , , , ,
, , , , , , ,
p,p,p,p,p,p,p,p
r,n,b,q,k,b,n,r
R,N,B,Q,K,B,N,R
P,P,P,P,P,P,P,P
, , , , , , ,
, , , , , , ,
, , , ,p, , ,
, , , , , , ,
p,p,p,p, ,p,p,p
r,n,b,q,k,b,n,r
js const values = [];
for (let x = 0; x < 10; x++) {
values.push([2 ** x, 2 * x ** 2]);
}
console.table(values);
结果为:
// The first column is the index
0 1 0
1 2 2
2 4 8
3 8 18
4 16 32
5 32 50
6 64 72
7 128 98
8 256 128
9 512 162
迭代方法不会改变调用它的数组,但作为 callbackFn 提供的函数可以。要记住关键原则是只有 0 和 arrayLength - 1 之间的索引可访问,arrayLength 是数组方法第一次被调用时的长度值,但传递给回调的元素是访问索引时的值。因此:
callbackFn 不会访问任何添加到超出数组的初始化长度之外的元素,当开始调用迭代方法时。
- 对已访问索引的变更不会导致再次对其调用
callbackFn。
- 如果数组中存在但未访问的元素被
callbackFn 更改,则传递给 callbackFn 的值将是访问该元素时的值。删除的元素不会被访问。
警告:上述类型的并发修改通常会导致代码难以理解,需要避免(特殊情况除外)。
下面的示例使用 forEach 方法作为一个例子,但以升序访问索引的其他的方法以相同的方式工作。我们将先定义一个帮助函数:
js function testSideEffect(effect) {
const arr = ["e1", "e2", "e3", "e4"];
arr.forEach((elem, index, arr) => {
console.log(`数组:[${arr.join(", ")}],索引:${index},元素:${elem}`);
effect(arr, index);
});
console.log(`最终数组:[${arr.join(", ")}]`);
}
一旦达到索引,对尚未访问索引的修改将可见:
js testSideEffect((arr, index) => {
if (index + 1 < arr.length) arr[index + 1] += "*";
});
// 数组:[e1, e2, e3, e4],索引:0,元素:e1
// 数组:[e1, e2*, e3, e4],索引:1,元素:e2*
// 数组:[e1, e2*, e3*, e4],索引:2,元素:e3*
// 数组:[e1, e2*, e3*, e4*],索引:3,元素:e4*
// 最终数组:[e1, e2*, e3*, e4*]
对已访问索引的修改不会改变迭代行为,尽管之后数据会有所不同:
js testSideEffect((arr, index) => {
if (index > 0) arr[index - 1] += "*";
});
// 数组:[e1, e2, e3, e4],索引:0,元素:e1
// 数组:[e1, e2, e3, e4],索引:1,元素:e2
// 数组:[e1*, e2, e3, e4],索引:2,元素:e3
// 数组:[e1*, e2*, e3, e4],索引:3,元素:e4
// 最终数组:[e1*, e2*, e3*, e4]
在小于初始数组长度的未访问索引处插入 n 元素将使它们被访问。原始数组中大于初始长度的最后 n 元素将不会被访问:
js testSideEffect((arr, index) => {
if (index === 1) arr.splice(2, 0, "new");
});
// 数组:[e1, e2, e3, e4],索引:0,元素:e1
// 数组:[e1, e2, e3, e4],索引:1,元素:e2
// 数组:[e1, e2, new, e3, e4],索引:2,元素:new
// 数组:[e1, e2, new, e3, e4],索引:3,元素:e3
// 最终数组:[e1, e2, new, e3, e4]
// e4 不会被访问因为它的索引是 4
在大于初始数组长度处插入 n 元素将不会使它们被访问:
js testSideEffect((arr) => arr.push("new"));
// 数组:[e1, e2, e3, e4],索引:0,元素:e1
// 数组:[e1, e2, e3, e4, new],索引:1,元素:e2
// 数组:[e1, e2, e3, e4, new, new],索引:2,元素:e3
// 数组:[e1, e2, e3, e4, new, new, new],索引:3,元素:e4
// 最终数组:[e1, e2, e3, e4, new, new, new, new]
对已经访问过的索引插入 n 元素将不会使它们被访问,但它会将剩余元素向后移动 n,因此当前索引和它之前的 n - 1 元素会再次被访问。
js testSideEffect((arr, index) => arr.splice(index, 0, "new"));
// 数组:[e1, e2, e3, e4],索引:0,元素:e1
// 数组:[new, e1, e2, e3, e4],索引:1,元素:e1
// 数组:[new, new, e1, e2, e3, e4],索引:2,元素:e1
// 数组:[new, new, new, e1, e2, e3, e4],索引:3,元素:e1
// 最终数组:[new, new, new, new, e1, e2, e3, e4]
// e1 不断被访问,因为它不断被移回
删除未访问索引处的 n 元素将使它们不再被访问。因为数组已经缩小,最后 n 迭代将访问越界索引。如果此方法忽略不存在的索引(参见数组方法和空槽),最后的 n 次迭代将会被跳过;否则,它们将收到 undefined:
js testSideEffect((arr, index) => {
if (index === 1) arr.splice(2, 1);
});
// 数组:[e1, e2, e3, e4],索引:0,元素:e1
// 数组:[e1, e2, e3, e4],索引:1,元素:e2
// 数组:[e1, e2, e4],索引:2,元素:e4
// 最终数组:[e1, e2, e4]
// 不会访问索引 3 因为它越界
// 与 find() 比较,后者将不存在的索引视为未定义:
const arr2 = ["e1", "e2", "e3", "e4"];
arr2.find((elem, index, arr) => {
console.log(`数组:[${arr.join(", ")}],索引:${index},元素:${elem}`);
if (index === 1) arr.splice(2, 1);
return false;
});
// 数组:[e1, e2, e3, e4],索引:0,元素:e1
// 数组:[e1, e2, e3, e4],索引:1,元素:e2
// 数组:[e1, e2, e4],索引:2,元素:e4
// 数组:[e1, e2, e4],索引:3,元素:undefined
删除已访问索引处的 n 元素不会改变它们在删除之前被访问的事实。因为数据已经缩小,在当前索引后的下 n 元素被跳过。如果此方法忽略不存在索引,最后的 n 次迭代将会被跳过;否则,它们将收到 undefined:
js testSideEffect((arr, index) => arr.splice(index, 1));
// 数组:[e1, e2, e3, e4],索引:0,元素:e1
// 不会范围内 e2 因为 e2 现在的索引是 0,索引 0 已经被访问过
// 数组:[e2, e3, e4],索引:1,元素:e3
// 不会范围内 e4 因为 e4 现在的索引是 1,索引 1 已经被访问过
// 最终数组:[e2, e4]
// 索引 2 越界,索引它不会被访问
// 与 find() 比较,后者将不存在的索引视为未定义:
const arr2 = ["e1", "e2", "e3", "e4"];
arr2.find((elem, index, arr) => {
console.log(`数组:[${arr.join(", ")}],索引:${index},元素:${elem}`);
arr.splice(index, 1);
return false;
});
// 数组:[e1, e2, e3, e4],索引:0,元素:e1
// 数组:[e2, e3, e4],索引:1,元素:e3
// 数组:[e2, e4],索引:2,元素:undefined
// 数组:[e2, e4],索引:3,元素:undefined
对于按索引降序迭代的方法,插入会导致元素被跳过,删除会导致元素被多次访问,调整上面的代码以查看效果。
|