In JavaScript, arrays aren't primitives but are instead Array
objects with the following core characteristics:
- JavaScript arrays are resizable and can contain a mix of different data types. (When those characteristics are undesirable, use typed arrays instead.)
- JavaScript arrays are not associative arrays and so, array elements cannot be accessed using arbitrary strings as indexes, but must be accessed using nonnegative integers (or their respective string form) as indexes.
- JavaScript arrays are zero-indexed: the first element of an array is at index
0
, the second is at index1
, and so on — and the last element is at the value of the array'slength
property minus1
. - JavaScript array-copy operations create shallow copies. (All standard built-in copy operations with any JavaScript objects create shallow copies, rather than deep copies).
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.
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:
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.
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
:
console.log(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.
A JavaScript array's length
property and numerical properties are connected.
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.
const fruits = [];
fruits.push("banana", "apple", "peach");
console.log(fruits.length); // 3
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:
fruits[5] = "mango";
console.log(fruits[5]); // 'mango'
console.log(Object.keys(fruits)); // ['0', '1', '2', '5']
console.log(fruits.length); // 6
Increasing the length
extends the array by adding empty slots without creating any new elements — not even undefined
.
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
Decreasing the length
property does, however, delete elements.
fruits.length = 2;
console.log(Object.keys(fruits)); // ['0', '1']
console.log(fruits.length); // 2
This is explained further on the length
page.
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
.
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.
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']
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()
.
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']
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:
- Objects: the object reference is copied into the new array. Both the original and new array refer to the same object. That is, if a referenced object is modified, the changes are visible to both the new and original arrays.
- Primitive types such as strings, numbers and booleans (not
String
,Number
, andBoolean
objects): their values are copied into the new array.
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.
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).
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:
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:
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
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:
method(callbackFn, thisArg)
Where callbackFn
takes three arguments:
The current element being processed in the array.
The index of the current element being processed in the array.
The array that the method was called upon.
What callbackFn
is expected to return depends on the array method that was called.
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.
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.
All iterative methods are copying and generic, although they behave differently with empty slots.
The following methods are iterative: every()
, filter()
, find()
, findIndex()
, findLast()
, findLastIndex()
, flatMap()
, forEach()
, map()
, and some()
.
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.
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
).
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):
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:
- Not all methods do the
i in this
test. Thefind
,findIndex
,findLast
, andfindLastIndex
methods do not, but other methods do. - The
length
is memorized before the loop starts. This affects how insertions and deletions during iteration are handled (see mutating initial array in iterative methods). - The method doesn't memorize the array contents, so if any index is modified during iteration, the new value might be observed.
- The code above iterates the array in ascending order of index. Some methods iterate in descending order of index (
for (let i = length - 1; i >= 0; i--)
):reduceRight()
,findLast()
, andfindLastIndex()
. reduce
andreduceRight
have slightly different signatures and do not always start at the first/last element.
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.
const arrayLike = {
0: "a",
1: "b",
length: 2,
};
console.log(Array.prototype.join.call(arrayLike, "+")); // 'a+b'
Normalization of the length property
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
.
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.
Array.prototype.flat.call({}); // []
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.
const a = { length: 0.7 };
Array.prototype.push.call(a);
console.log(a.length); // 0
Array-like objects
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.
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.
function f() {
console.log(Array.prototype.join.call(arguments, "+"));
}
f("a", "b"); // 'a+b'
Creates a new Array
object.
Returns the Array
constructor.
Creates a new Array
instance from an iterable or array-like object.
Creates a new Array
instance from an async iterable, iterable, or array-like object.
Returns true
if the argument is an array, or false
otherwise.
Creates a new Array
instance with a variable number of arguments, regardless of number or type of the arguments.
These properties are defined on Array.prototype
and shared by all Array
instances.
The constructor function that created the instance object. For Array
instances, the initial value is the Array
constructor.
Contains property names that were not included in the ECMAScript standard prior to the ES2015 version and that are ignored for with
statement-binding purposes.
These properties are own properties of each Array
instance.
Reflects the number of elements in an array.
Returns the array item at the given index. Accepts negative integers, which count back from the last item.
Returns a new array that is the calling array joined with other array(s) and/or value(s).
Copies a sequence of array elements within an array.
Returns a new array iterator object that contains the key/value pairs for each index in an array.
Returns true
if every element in the calling array satisfies the testing function.
Fills all the elements of an array from a start index to an end index with a static value.
Returns a new array containing all elements of the calling array for which the provided filtering function returns true
.
Returns the value of the first element in the array that satisfies the provided testing function, or undefined
if no appropriate element is found.
Returns the index of the first element in the array that satisfies the provided testing function, or -1
if no appropriate element was found.
Returns the value of the last element in the array that satisfies the provided testing function, or undefined
if no appropriate element is found.
Returns the index of the last element in the array that satisfies the provided testing function, or -1
if no appropriate element was found.
Returns a new array with all sub-array elements concatenated into it recursively up to the specified depth.
Returns a new array formed by applying a given callback function to each element of the calling array, and then flattening the result by one level.
Calls a function for each element in the calling array.
Determines whether the calling array contains a value, returning true
or false
as appropriate.
Returns the first (least) index at which a given element can be found in the calling array.
Joins all elements of an array into a string.
Returns a new array iterator that contains the keys for each index in the calling array.
Returns the last (greatest) index at which a given element can be found in the calling array, or -1
if none is found.
Returns a new array containing the results of invoking a function on every element in the calling array.
Removes the last element from an array and returns that element.
Adds one or more elements to the end of an array, and returns the new length
of the array.
Executes a user-supplied "reducer" callback function on each element of the array (from left to right), to reduce it to a single value.
Executes a user-supplied "reducer" callback function on each element of the array (from right to left), to reduce it to a single value.
Reverses the order of the elements of an array in place. (First becomes the last, last becomes first.)
Removes the first element from an array and returns that element.
Extracts a section of the calling array and returns a new array.
Returns true
if at least one element in the calling array satisfies the provided testing function.
Sorts the elements of an array in place and returns the array.
Adds and/or removes elements from an array.
Returns a localized string representing the calling array and its elements. Overrides the Object.prototype.toLocaleString()
method.
Returns a new array with the elements in reversed order, without modifying the original array.
Returns a new array with the elements sorted in ascending order, without modifying the original array.
Returns a new array with some elements removed and/or replaced at a given index, without modifying the original array.
Returns a string representing the calling array and its elements. Overrides the Object.prototype.toString()
method.
Adds one or more elements to the front of an array, and returns the new length
of the array.
Returns a new array iterator object that contains the values for each index in the array.
Returns a new array with the element at the given index replaced with the given value, without modifying the original array.
An alias for the values()
method by default.
This section provides some examples of common array operations in JavaScript.
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.
// '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
This example uses the join()
method to create a string from the fruits
array.
const fruits = ["Apple", "Banana"];
const fruitsString = fruits.join(", ");
console.log(fruitsString);
// "Apple, Banana"
This example shows how to access items in the fruits
array by specifying the index number of their position in the array.
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
This example uses the indexOf()
method to find the position (index) of the string "Banana"
in the fruits
array.
const fruits = ["Apple", "Banana"];
console.log(fruits.indexOf("Banana"));
// 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
.
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
This example uses the push()
method to append a new string to the fruits
array.
const fruits = ["Apple", "Banana"];
const newLength = fruits.push("Orange");
console.log(fruits);
// ["Apple", "Banana", "Orange"]
console.log(newLength);
// 3
This example uses the pop()
method to remove the last item from the fruits
array.
const fruits = ["Apple", "Banana", "Orange"];
const removedItem = fruits.pop();
console.log(fruits);
// ["Apple", "Banana"]
console.log(removedItem);
// Orange
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.
This example uses the splice()
method to remove the last 3 items from the fruits
array.
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"]
This example uses the splice()
method to truncate the fruits
array down to just its first 2 items.
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"]
This example uses the shift()
method to remove the first item from the fruits
array.
const fruits = ["Apple", "Banana"];
const removedItem = fruits.shift();
console.log(fruits);
// ["Banana"]
console.log(removedItem);
// Apple
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.
This example uses the splice()
method to remove the first 3 items from the fruits
array.
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"]
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.
const fruits = ["Banana", "Mango"];
const newLength = fruits.unshift("Strawberry");
console.log(fruits);
// ["Strawberry", "Banana", "Mango"]
console.log(newLength);
// 3
This example uses the splice()
method to remove the string "Banana"
from the fruits
array — by specifying the index position of "Banana"
.
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"]
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.
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"]
This example uses the splice()
method to replace the last 2 items in the fruits
array with new items.
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"]
This example uses a for...of
loop to iterate over the fruits
array, logging each item to the console.
const fruits = ["Apple", "Mango", "Cherry"];
for (const fruit of fruits) {
console.log(fruit);
}
// Apple
// Mango
// Cherry
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.
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.
const fruits = ["Apple", "Mango", "Cherry"];
fruits.forEach((item, index, array) => {
console.log(item, index);
});
// Apple 0
// Mango 1
// Cherry 2
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.
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"]
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.
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"]
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.
const fruitsDeepCopy = JSON.parse(JSON.stringify(fruits));
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:
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']
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.
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
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
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:
// 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" ]
For more information about the result of a match, see the RegExp.prototype.exec()
and String.prototype.match()
pages.
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
will not visit any elements added beyond the array's initial length when the call to the iterative method began.- Changes to already-visited indexes do not cause
callbackFn
to be invoked on them again. - If an existing, yet-unvisited element of the array is changed by
callbackFn
, its value passed to thecallbackFn
will be the value at the time that element gets visited. Removed elements are not visited.
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).
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:
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:
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:
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]
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:
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
Inserting n elements with index greater than the initial array length will not make them be visited:
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]
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:
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
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
:
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
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
:
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
BCD tables only load in the browser