- Chapter 18. Arrays
- Overview
- Creating Arrays
- Array Indices
- length
- Holes in Arrays
- Array Constructor Method
- Array Prototype Methods
- Adding and Removing Elements (Destructive)
- Sorting and Reversing Elements (Destructive)
- Concatenating, Slicing, Joining (Nondestructive)
- Searching for Values (Nondestructive)
- Iteration (Nondestructive)
- Pitfall: Array-Like Objects
- Best Practices: Iterating over Arrays
buy the book to support the author.
Chapter 18. Arrays
An array is a map from indices (natural numbers, starting at zero) to arbitrary values. The values (the range of the map) are called the array’s elements. The most convenient way of creating an array is via an array literal. Such a literal enumerates the array elements; an element’s position implicitly specifies its index.
In this chapter, I will first cover basic array mechanisms, such as indexed access and the length
property, and then go over array methods.
Overview
This section provides a quick overview of arrays. Details are explained later.
As a first example, we create an array arr
via an array literal (see Creating Arrays) and access elements (see Array Indices):
- > var arr = [ 'a', 'b', 'c' ]; // array literal
- > arr[0] // get element 0
- 'a'
- > arr[0] = 'x'; // set element 0
- > arr
- [ 'x', 'b', 'c' ]
We can use the array property length
(see length) to remove and append elements:
- > var arr = [ 'a', 'b', 'c' ];
- > arr.length
- 3
- > arr.length = 2; // remove an element
- > arr
- [ 'a', 'b' ]
- > arr[arr.length] = 'd'; // append an element
- > arr
- [ 'a', 'b', 'd' ]
The array method push()
provides another way of appending an element:
- > var arr = [ 'a', 'b' ];
- > arr.push('d')
- 3
- > arr
- [ 'a', 'b', 'd' ]
Arrays Are Maps, Not Tuples
The ECMAScript standard specifies arrays as maps (dictionaries) from indices to values. In other words, arrays may not be contiguous and can have holes in them. For example:
- > var arr = [];
- > arr[0] = 'a';
- 'a'
- > arr[2] = 'b';
- 'b'
- > arr
- [ 'a', , 'b' ]
The preceding array has a hole: there is no element at index 1. Holes in Arrays explains holes in more detail.
Note that most JavaScript engines optimize arrays without holes internally and store them contiguously.
Arrays Can Also Have Properties
Arrays are still objects and can have object properties. Those are not considered part of the actual array; that is, they are not considered array elements:
- > var arr = [ 'a', 'b' ];
- > arr.foo = 123;
- > arr
- [ 'a', 'b' ]
- > arr.foo
- 123
Creating Arrays
You create an array via an array literal:
var
myArray
=
[
'a'
,
'b'
,
'c'
];
Trailing commas in arrays are ignored:
- > [ 'a', 'b' ].length
- 2
- > [ 'a', 'b', ].length
- 2
- > [ 'a', 'b', ,].length // hole + trailing comma
- 3
The Array Constructor
There are two ways to use the constructor Array
: you can create an empty array with a given length or an array whose elements are the given values. For this constructor, new
is optional: invoking it as a normal function (without new
) does the same as invoking it as a constructor.
Creating an empty array with a given length
- > var arr = new Array(2);
- > arr.length
- 2
- > arr // two holes plus trailing comma (ignored!)
- [ , ,]
Some engines may preallocate contiguous memory when you call Array()
in this manner, which may slightly improve performance. However, be sure that the increased verbosity and redundancy is worth it!
Initializing an array with elements (avoid!)
This way of invoking Array
is similar to an array literal:
// The same as ['a', 'b', 'c']:
var
arr1
=
new
Array
(
'a'
,
'b'
,
'c'
);
The problem is that you can’t create arrays with a single number in them, because that is interpreted as creating an array whose length
is the number:
- > new Array(2) // alas, not [ 2 ]
- [ , ,]
- > new Array(5.7) // alas, not [ 5.7 ]
- RangeError: Invalid array length
- > new Array('abc') // ok
- [ 'abc' ]
Multidimensional Arrays
If you need multiple dimensions for elements, you must nest arrays. When you create such nested arrays, the innermost arrays can grow as needed. But if you want direct access to elements, you need to at least create the outer arrays. In the following example, I create a three-by-three matrix for Tic-tac-toe. The matrix is completely filled with data (as opposed to letting rows grow as needed):
// Create the Tic-tac-toe board
var
rows
=
[];
for
(
var
rowCount
=
0
;
rowCount
<
3
;
rowCount
++
)
{
rows
[
rowCount
]
=
[];
for
(
var
colCount
=
0
;
colCount
<
3
;
colCount
++
)
{
rows
[
rowCount
][
colCount
]
=
'.'
;
}
}
// Set an X in the upper right corner
rows
[
0
][
2
]
=
'X'
;
// [row][column]
// Print the board
rows
.
forEach
(
function
(
row
)
{
console
.
log
(
row
.
join
(
' '
));
});
Here is the output:
- . . X
- . . .
- . . .
I wanted the example to demonstrate the general case.Obviously, if a matrix is so small and has fixed dimensions, you can set it up via an array literal:
var
rows
=
[
[
'.'
,
'.'
,
'.'
],
[
'.'
,
'.'
,
'.'
],
[
'.'
,
'.'
,
'.'
]
];
Array Indices
When you are working with array indices, you must keep in mind the following limits:
- Indices are numbers i in the range 0 ≤ i < 232−1.
- The maximum length is 232−1.
Indices that are out of range are treated as normal property keys (strings!). They don’t show up as array elements and they don’t influence the property length
. For example:
- > var arr = [];
- > arr[-1] = 'a';
- > arr
- []
- > arr['-1']
- 'a'
- > arr[4294967296] = 'b';
- > arr
- []
- > arr['4294967296']
- 'b'
The in Operator and Indices
The in
operator detects whether an object has a property with a given key. But it can also be used to determine whether a given element index exists in an array. For example:
- > var arr = [ 'a', , 'b' ];
- > 0 in arr
- true
- > 1 in arr
- false
- > 10 in arr
- false
Deleting Array Elements
In addition to deleting properties, the delete
operator also deletes array elements. Deleting elements creates holes (the length
property is not updated):
- > var arr = [ 'a', 'b' ];
- > arr.length
- 2
- > delete arr[1] // does not update length
- true
- > arr
- [ 'a', ]
- > arr.length
- 2
You can also delete trailing array elements by decreasing an array’s length (see length for details). To remove elements without creating holes (i.e., the indices of subsequent elements are decremented), you use Array.prototype.splice()
(see Adding and Removing Elements (Destructive)). In this example, we remove two elements at index 1:
- > var arr = ['a', 'b', 'c', 'd'];
- > arr.splice(1, 2) // returns what has been removed
- [ 'b', 'c' ]
- > arr
- [ 'a', 'd' ]
Array Indices in Detail
Tip
This is an advanced section. You normally don’t need to know the details explained here.
_Array indices are not what they seem._Until now, I have pretended that array indices are numbers. And that is how JavaScript engines implement arrays, internally. However, the ECMAScript specification sees indices differently. Paraphrasing Section 15.4:
- A property key
P
(a string) is an array index if and only ifToString
(ToUint32(P))
is equal toP
andToUint32(P)
is not equal to 232−1. What this means is explained momentarily. - An array property whose key is an array index is called an element.
In other words, in the world of the spec all values in brackets are converted to strings and interpreted as property keys, even numbers. The following interaction demonstrates this:
- > var arr = ['a', 'b'];
- > arr['0']
- 'a'
- > arr[0]
- 'a'
To be an array index, a property key P
(a string!) must be equal to the result of the following computation:
- Convert
P
to a number. - Convert the number to a 32-bit unsigned integer.
- Convert the integer to a string.
That means that an array index must be a stringified integer i in the 32-bit range 0 ≤ i < 232−1. The upper limit has been explicitly excluded in the spec (as quoted previously). It is reserved for the maximum length. To see how this definition works, let’s use the function ToUint32()
from 32-bit Integers via Bitwise Operators.
First, a string that doesn’t contain a number is always converted to 0, which, after stringification, is not equal to the string:
- > ToUint32('xyz')
- 0
- > ToUint32('?@#!')
- 0
Second, a stringified integer that is out of range is also converted to a completely different integer, which is not equal to the string, after stringification:
- > ToUint32('-1')
- 4294967295
- > Math.pow(2, 32)
- 4294967296
- > ToUint32('4294967296')
- 0
Third, stringified noninteger numbers are converted to integers, which are, again, different:
- > ToUint32('1.371')
- 1
Note that the specification also enforces that array indices don’t have exponents:
- > ToUint32('1e3')
- 1000
And that they don’t have leading zeros:
- > var arr = ['a', 'b'];
- > arr['0'] // array index
- 'a'
- > arr['00'] // normal property
- undefined
length
The basic function of the length
property is to track the highest index in an array:
- > [ 'a', 'b' ].length
- 2
- > [ 'a', , 'b' ].length
- 3
Thus, length
does not count the number of elements, so you’d have to write your own function for doing so. For example:
function
countElements
(
arr
)
{
var
elemCount
=
0
;
arr
.
forEach
(
function
()
{
elemCount
++
;
});
return
elemCount
;
}
To count elements (nonholes), we have used the fact that forEach
skips holes.Here is the interaction:
- > countElements([ 'a', 'b' ])
- 2
- > countElements([ 'a', , 'b' ])
- 2
Manually Increasing the Length of an Array
Manually increasing the length of an array has remarkably little effect on an array; it only creates holes:
- > var arr = [ 'a', 'b' ];
- > arr.length = 3;
- > arr // one hole at the end
- [ 'a', 'b', ,]
The last result has two commas at the end, because a trailing comma is optional and thus always ignored.
What we just did did not add any elements:
- > countElements(arr)
- 2
However, the length
property does act as a pointer indicating where to insert new elements. For example:
- > arr.push('c')
- 4
- > arr
- [ 'a', 'b', , 'c' ]
Thus, setting the initial length of an array via the Array
constructor creates an array that is completely empty:
- > var arr = new Array(2);
- > arr.length
- 2
- > countElements(arr)
- 0
Decreasing the Length of an Array
If you decrease the length of an array, all elements at the new length and above are deleted:
- > var arr = [ 'a', 'b', 'c' ];
- > 1 in arr
- true
- > arr[1]
- 'b'
- > arr.length = 1;
- > arr
- [ 'a' ]
- > 1 in arr
- false
- > arr[1]
- undefined
Clearing an array
If you set an array’s length to 0, then it becomes empty. That allows you to clear an array for someone else. For example:
function
clearArray
(
arr
)
{
arr
.
length
=
0
;
}
Here’s the interaction:
- > var arr = [ 'a', 'b', 'c' ];
- > clearArray(arr)
- > arr
- []
Note, however, that this approach can be slow, because each array element is explicitly deleted. Ironically, creating a new empty array is often faster:
arr
=
[];
Clearing shared arrays
You need to be aware of the fact that setting an array’s length to zero affects everybody who shares the array:
>
var
a1
=
[
1
,
2
,
3
];
>
var
a2
=
a1
;
>
a1
.
length
=
0
;
>
a1
[]
>
a2
[]
In contrast, assigning an empty array doesn’t:
>
var
a1
=
[
1
,
2
,
3
];
>
var
a2
=
a1
;
>
a1
=
[];
>
a1
[]
>
a2
[
1
,
2
,
3
]
The Maximum Length
The maximum array length is 232−1:
- > var arr1 = new Array(Math.pow(2, 32)); // not ok
- RangeError: Invalid array length
- > var arr2 = new Array(Math.pow(2, 32)-1); // ok
- > arr2.push('x');
- RangeError: Invalid array length
Holes in Arrays
Arrays are maps from indices to values. That means that arrays can have holes, indices smaller than the length that are missing in the array. Reading an element at one of those indices returns undefined
.
Tip
It is recommended that you avoid holes in arrays. JavaScript handles them inconsistently (i.e., some methods ignore them, other don’t). Thankfully, you normally don’t need to know how holes are handled: they are rarely useful and affect performance negatively.
Creating Holes
You can create holes by assigning to array indices:
- > var arr = [];
- > arr[0] = 'a';
- > arr[2] = 'c';
- > 1 in arr // hole at index 1
- false
You can also create holes by omitting values in array literals:
- > var arr = ['a',,'c'];
- > 1 in arr // hole at index 1
- false
Warning
You need two trailing commas to create a trailing hole, because the last comma is always ignored:
- > [ 'a', ].length
- 1
- > [ 'a', ,].length
- 2
Sparse Arrays Versus Dense Arrays
This section examines the differences between a hole and undefined
as an element. Given that reading a hole returns undefined
, both are very similar.
An array with holes is called sparse. An array without holes is called dense. Dense arrays are contiguous and have an element at each index—starting at zero, and ending at length
− 1. Let’s compare the following two arrays, a sparse array and a dense array. The two are very similar:
var
sparse
=
[
,
,
'c'
];
var
dense
=
[
undefined
,
undefined
,
'c'
];
A hole is almost like having the element undefined
at the same index. Both arrays have the same length:
- > sparse.length
- 3
- > dense.length
- 3
But the sparse array does not have an element at index 0:
- > 0 in sparse
- false
- > 0 in dense
- true
Iteration via for
is the same for both arrays:
- > for (var i=0; i<sparse.length; i++) console.log(sparse[i]);
- undefined
- undefined
- c
- > for (var i=0; i<dense.length; i++) console.log(dense[i]);
- undefined
- undefined
- c
Iteration via forEach
skips the holes, but not the undefined elements:
- > sparse.forEach(function (x) { console.log(x) });
- c
- > dense.forEach(function (x) { console.log(x) });
- undefined
- undefined
- c
Which Operations Ignore Holes, and Which Consider Them?
Some operations involving arrays ignore holes, while others consider them. This sections explains the details.
Array iteration methods
forEach()
skips holes:
- > ['a',, 'b'].forEach(function (x,i) { console.log(i+'.'+x) })
- 0.a
- 2.b
every()
also skips holes (similarly: some()
):
- > ['a',, 'b'].every(function (x) { return typeof x === 'string' })
- true
map()
skips, but preserves holes:
- > ['a',, 'b'].map(function (x,i) { return i+'.'+x })
- [ '0.a', , '2.b' ]
filter()
eliminates holes:
- > ['a',, 'b'].filter(function (x) { return true })
- [ 'a', 'b' ]
Other array methods
join()
converts holes, undefined
s, and null
s to empty strings:
- > ['a',, 'b'].join('-')
- 'a--b'
- > [ 'a', undefined, 'b' ].join('-')
- 'a--b'
sort()
preserves holes while sorting:
- > ['a',, 'b'].sort() // length of result is 3
- [ 'a', 'b', , ]
The for-in loop
The for-in
loop correctly lists property keys (which are a superset of array indices):
- > for (var key in ['a',, 'b']) { console.log(key) }
- 0
- 2
Function.prototype.apply()
apply()
turns each hole into an argument whose value is undefined
. The following interaction demonstrates this: function f()
returns its arguments as an array. When we pass apply()
an array with three holes in order to invoke f()
, the latter receives three undefined
arguments:
- > function f() { return [].slice.call(arguments) }
- > f.apply(null, [ , , ,])
- [ undefined, undefined, undefined ]
That means that we can use apply()
to create an array with undefined
s:
- > Array.apply(null, Array(3))
- [ undefined, undefined, undefined ]
Warning
apply()
translates holes to undefined
s in empty arrays, but it can’t be used to plug holes in arbitrary arrays (which may or may not contain holes). Take, for example, the arbitrary array [2]
:
- > Array.apply(null, [2])
- [ , ,]
The array does not contain any holes, so apply()
should return the same array. Instead, it returns an empty array with length 2 (all it contains are two holes). That is because Array()
interprets single numbers as array lengths, not as array elements.
Removing Holes from Arrays
As we have seen, filter()
removes holes:
- > ['a',, 'b'].filter(function (x) { return true })
- [ 'a', 'b' ]
Use a custom function to convert holes to undefined
s in arbitrary arrays:
function
convertHolesToUndefineds
(
arr
)
{
var
result
=
[];
for
(
var
i
=
0
;
i
<
arr
.
length
;
i
++
)
{
result
[
i
]
=
arr
[
i
];
}
return
result
;
}
Using the function:
- > convertHolesToUndefineds(['a',, 'b'])
- [ 'a', undefined, 'b' ]
Array Constructor Method
Array.isArray(obj)
- Returns
true
ifobj
is an array. It correctly handles objects that cross realms (windows or frames)—as opposed toinstanceof
(see Pitfall: crossing realms (frames or windows)).
Array Prototype Methods
In the following sections, array prototype methods are grouped by functionality. For each of the subsections, I mention whether the methods are destructive (they change the arrays that they are invoked on) or nondestructive (they don’t modify their receivers; such methods often return new arrays).
Adding and Removing Elements (Destructive)
All of the methods in this section are destructive:
Array.prototype.shift()
- Removes the element at index 0 and returns it. The indices of subsequent elements are decremented by 1:
- > var arr = [ 'a', 'b' ];
- > arr.shift()
- 'a'
- > arr
- [ 'b' ]
Array.prototype.unshift(elem1?, elem2?, …)
- Prepends the given elements to the array. It returns the new length:
- > var arr = [ 'c', 'd' ];
- > arr.unshift('a', 'b')
- 4
- > arr
- [ 'a', 'b', 'c', 'd' ]
Array.prototype.pop()
- Removes the last element of the array and returns it:
- > var arr = [ 'a', 'b' ];
- > arr.pop()
- 'b'
- > arr
- [ 'a' ]
Array.prototype.push(elem1?, elem2?, …)
- Adds the given elements to the end of the array. It returns the new length:
- > var arr = [ 'a', 'b' ];
- > arr.push('c', 'd')
- 4
- > arr
- [ 'a', 'b', 'c', 'd' ]
apply()
(see Function.prototype.apply(thisValue, argArray)) enables you to destructively append an array arr2
to another array arr1
:
- > var arr1 = [ 'a', 'b' ];
- > var arr2 = [ 'c', 'd' ];
- > Array.prototype.push.apply(arr1, arr2)
- 4
- > arr1
- [ 'a', 'b', 'c', 'd' ]
Array.prototype.splice(start, deleteCount?, elem1?, elem2?, …)
- Starting at
start
, removesdeleteCount
elements and inserts the elements given. In other words, you are replacing thedeleteCount
elements at positionstart
withelem1
,elem2
, and so on. The method returns the elements that have been removed:
- > var arr = [ 'a', 'b', 'c', 'd' ];
- > arr.splice(1, 2, 'X');
- [ 'b', 'c' ]
- > arr
- [ 'a', 'X', 'd' ]
Special parameter values:
start
can be negative, in which case it is added to the length to determine the start index. Thus,-1
refers the last element, and so on.deleteCount
is optional. If it is omitted (along with all subsequent arguments), then all elements at and after indexstart
are removed.
In this example, we remove all elements after and including the second-to-last index:
- > var arr = [ 'a', 'b', 'c', 'd' ];
- > arr.splice(-2)
- [ 'c', 'd' ]
- > arr
- [ 'a', 'b' ]
Sorting and Reversing Elements (Destructive)
These methods are also destructive:
Array.prototype.reverse()
- Reverses the order of the elements in the array and returns a reference to the original (modified) array:
- > var arr = [ 'a', 'b', 'c' ];
- > arr.reverse()
- [ 'c', 'b', 'a' ]
- > arr // reversing happened in place
- [ 'c', 'b', 'a' ]
Array.prototype.sort(compareFunction?)
- Sorts the array and returns it:
- > var arr = ['banana', 'apple', 'pear', 'orange'];
- > arr.sort()
- [ 'apple', 'banana', 'orange', 'pear' ]
- > arr // sorting happened in place
- [ 'apple', 'banana', 'orange', 'pear' ]
Keep in mind that sorting compares values by converting them to strings, which means that numbers are not sorted numerically:
- > [-1, -20, 7, 50].sort()
- [ -1, -20, 50, 7 ]
You can fix this by providing the optional parameter compareFunction
, which controls how sorting is done. It has the following signature:
function
compareFunction
(
a
,
b
)
This function compares a
and b
and returns:
- An integer less than zero (e.g.,
-1
) ifa
is less thanb
- Zero if
a
is equal tob
- An integer greater than zero (e.g.,
1
) ifa
is greater thanb
Comparing Numbers
For numbers, you can simply return a-b
, but that can cause numeric overflow. To prevent that from happening, you need more verbose code:
function
compareCanonically
(
a
,
b
)
{
if
(
a
<
b
)
{
return
-
1
;
}
else
if
(
a
>
b
)
{
return
1
;
}
else
{
return
0
;
}
}
I don’t like nested conditional operators. But in this case, the code is so much less verbose that I’m tempted to recommend it:
function
compareCanonically
(
a
,
b
)
{
return
a
<
b
?
-
1
:
(
a
>
b
?
1
:
0
);
}
Using the function:
- > [-1, -20, 7, 50].sort(compareCanonically)
- [ -20, -1, 7, 50 ]
Comparing Strings
For strings, you can use String.prototype.localeCompare
(see Comparing Strings):
- > ['c', 'a', 'b'].sort(function (a,b) { return a.localeCompare(b) })
- [ 'a', 'b', 'c' ]
Comparing Objects
The parameter compareFunction
is also useful for sorting objects:
var
arr
=
[
{
name
:
'Tarzan'
},
{
name
:
'Cheeta'
},
{
name
:
'Jane'
}
];
function
compareNames
(
a
,
b
)
{
return
a
.
name
.
localeCompare
(
b
.
name
);
}
With compareNames
as the compare function, arr
is sorted by name
:
- > arr.sort(compareNames)
- [ { name: 'Cheeta' },
- { name: 'Jane' },
- { name: 'Tarzan' } ]
Concatenating, Slicing, Joining (Nondestructive)
The following methods perform various nondestructive operations on arrays:
Array.prototype.concat(arr1?, arr2?, …)
- Creates a new array that contains all the elements of the receiver, followed by all the elements of the array
arr1
, and so on. If one of the parameters is not an array, then it is added to the result as an element (for example, the first argument,'c'
, here):
- > var arr = [ 'a', 'b' ];
- > arr.concat('c', ['d', 'e'])
- [ 'a', 'b', 'c', 'd', 'e' ]
The array that concat()
is invoked on is not changed:
- > arr
- [ 'a', 'b' ]
Array.prototype.slice(begin?, end?)
- Copies array elements into a new array, starting at
begin
, until and excluding the element atend
:
- > [ 'a', 'b', 'c', 'd' ].slice(1, 3)
- [ 'b', 'c' ]
If end
is missing, the array length is used:
- > [ 'a', 'b', 'c', 'd' ].slice(1)
- [ 'b', 'c', 'd' ]
If both indices are missing, the array is copied:
- > [ 'a', 'b', 'c', 'd' ].slice()
- [ 'a', 'b', 'c', 'd' ]
If either of the indices is negative, the array length is added to it. Thus, -1
refers to the last element, and so on:
- > [ 'a', 'b', 'c', 'd' ].slice(1, -1)
- [ 'b', 'c' ]
- > [ 'a', 'b', 'c', 'd' ].slice(-2)
- [ 'c', 'd' ]
Array.prototype.join(separator?)
- Creates a string by applying
toString()
to all array elements and putting the string inseparator
between the results. Ifseparator
is omitted,','
is used:
- > [3, 4, 5].join('-')
- '3-4-5'
- > [3, 4, 5].join()
- '3,4,5'
- > [3, 4, 5].join('')
- '345'
join()
converts undefined
and null
to empty strings:
- > [undefined, null].join('#')
- '#'
Holes in arrays are also converted to empty strings:
- > ['a',, 'b'].join('-')
- 'a--b'
Searching for Values (Nondestructive)
The following methods search for values in arrays:
Array.prototype.indexOf(searchValue, startIndex?)
- Searches the array for
searchValue
, starting atstartIndex
. It returns the index of the first occurrence or –1 if nothing is found. IfstartIndex
is negative, the array length is added to it; if it is missing, the whole array is searched:
- > [ 3, 1, 17, 1, 4 ].indexOf(1)
- 1
- > [ 3, 1, 17, 1, 4 ].indexOf(1, 2)
- 3
Strict equality (see Equality Operators: === Versus ==) is used for the search, which means that indexOf()
can’t find NaN
:
- > [NaN].indexOf(NaN)
- -1
Array.prototype.lastIndexOf(searchElement, startIndex?)
- Searches the array for
searchElement
, starting atstartIndex
, backward. It returns the index of the first occurrence or –1 if nothing is found. IfstartIndex
is negative, the array length is added to it; if it is missing, the whole array is searched. Strict equality (see Equality Operators: === Versus ==) is used for the search:
- > [ 3, 1, 17, 1, 4 ].lastIndexOf(1)
- 3
- > [ 3, 1, 17, 1, 4 ].lastIndexOf(1, -3)
- 1
Iteration (Nondestructive)
Iteration methods use a function to iterate over an array. I distinguish three kinds of iteration methods, all of which are nondestructive: examination methods mainly observe the content of an array; transformation methods derive a new array from the receiver; and reduction methods compute a result based on the receiver’s elements.
Examination Methods
Each method described in this section looks like this:
arr
.
examinationMethod
(
callback
,
thisValue
?
)
Such a method takes the following parameters:
callback
is its first parameter, a function that it calls.Depending on the examination method, the callback returns a boolean or nothing. It has the following signature:
function
callback
(
element
,
index
,
array
)
element
is an array element for callback
to process, index
is the element’s index, and array
is the array that examinationMethod
has been invoked on.
thisValue
allows you to configure the value ofthis
insidecallback
.
And now for the examination methods whose signatures I have just described:
Array.prototype.forEach(callback, thisValue?)
- Iterates over the elements of an array:
var
arr
=
[
'apple'
,
'pear'
,
'orange'
];
arr
.
forEach
(
function
(
elem
)
{
console
.
log
(
elem
);
});
Array.prototype.every(callback, thisValue?)
- Returns
true
if the callback returnstrue
for every element. It stops iteration as soon as the callback returnsfalse
.Note that not returning a value leads to an implicit return ofundefined
, whichevery()
interprets asfalse
.every()
works like the universal quantifier (“for all”).
This example checks whether every number in the array is even:
- > function isEven(x) { return x % 2 === 0 }
- > [ 2, 4, 6 ].every(isEven)
- true
- > [ 2, 3, 4 ].every(isEven)
- false
If the array is empty, the result is true
(and callback
is not called):
- > [].every(function () { throw new Error() })
- true
Array.prototype.some(callback, thisValue?)
- Returns
true
if the callback returnstrue
for at least one element. It stops iteration as soon as the callback returnstrue
.Note that not returning a value leads to an implicit return ofundefined
, whichsome
interprets asfalse
.some()
works like the existential quantifier (“there exists”).
This example checks whether there is an even number in the array:
- > function isEven(x) { return x % 2 === 0 }
- > [ 1, 3, 5 ].some(isEven)
- false
- > [ 1, 2, 3 ].some(isEven)
- true
If the array is empty, the result is false
(and callback
is not called):
- > [].some(function () { throw new Error() })
- false
One potential pitfall of forEach()
is that it does not support break
or something similar to prematurely abort the loop. If you need to do that, you can use some()
:
function
breakAtEmptyString
(
strArr
)
{
strArr
.
some
(
function
(
elem
)
{
if
(
elem
.
length
===
0
)
{
return
true
;
// break
}
console
.
log
(
elem
);
// implicit: return undefined (interpreted as false)
});
}
some()
returns true
if a break happened, and false
otherwise. This allows you to react differently depending on whether iterating finished successfully (something that is slightly tricky with for
loops).
Transformation Methods
Transformation methods take an input array and produce an output array, while the callback controls how the output is produced. The callback has the same signature as for examination:
function
callback
(
element
,
index
,
array
)
There are two transformation methods:
Array.prototype.map(callback, thisValue?)
- Each output array element is the result of applying
callback
to an input element. For example:
- > [ 1, 2, 3 ].map(function (x) { return 2 * x })
- [ 2, 4, 6 ]
Array.prototype.filter(callback, thisValue?)
- The output array contains only those input elements for which
callback
returnstrue
. For example:
- > [ 1, 0, 3, 0 ].filter(function (x) { return x !== 0 })
- [ 1, 3 ]
Reduction Methods
For reducing, the callback has a different signature:
function
callback
(
previousValue
,
currentElement
,
currentIndex
,
array
)
The parameter previousValue
is the value previously returned by the callback. When the callback is first called, there are two possibilities (the descriptions are for Array.prototype.reduce()
; differences with reduceRight()
are mentioned in parentheses):
- An explicit
initialValue
has been provided. ThenpreviousValue
isinitialValue
, andcurrentElement
is the first array element (reduceRight
: the last array element). - No explicit
initialValue
has been provided. ThenpreviousValue
is the first array element, andcurrentElement
is the second array element (reduceRight
: the last array element and second-to-last array element).
There are two reduction methods:
Array.prototype.reduce(callback, initialValue?)
- Iterates from left to right and invokes the callback as previously sketched. The result of the method is the last value returned by the callback. This example computes the sum of all array elements:
function
add
(
prev
,
cur
)
{
return
prev
+
cur
;
}
console
.
log
([
10
,
3
,
-
1
].
reduce
(
add
));
// 12
If you invoke reduce
on an array with a single element, that element is returned:
- > [7].reduce(add)
- 7
If you invoke reduce
on an empty array, you must specify initialValue
, otherwise you get an exception:
- > [].reduce(add)
- TypeError: Reduce of empty array with no initial value
- > [].reduce(add, 123)
- 123
Array.prototype.reduceRight(callback, initialValue?)
- Works the same as
reduce()
, but iterates from right to left.
Note
In many functional programming languages, reduce
is known as fold
or foldl
(left fold) and reduceRight
is known as foldr
(right fold).
Another way to look at the reduce
method is that it implements an n-ary operator OP
:
OP1≤i≤n
xi
via a series of applications of a binary operator op2
:
(…(x1 op2
x2) op2
…) op2
xn
That’s what happened in the previous code example: we implemented an n-ary sum operator for arrays via JavaScript’s binary plus operator.
As an example, let’s examine the two iteration directions via the following function:
function
printArgs
(
prev
,
cur
,
i
)
{
console
.
log
(
'prev:'
+
prev
+
', cur:'
+
cur
+
', i:'
+
i
);
return
prev
+
cur
;
}
As expected, reduce()
iterates from left to right:
- > ['a', 'b', 'c'].reduce(printArgs)
- prev:a, cur:b, i:1
- prev:ab, cur:c, i:2
- 'abc'
- > ['a', 'b', 'c'].reduce(printArgs, 'x')
- prev:x, cur:a, i:0
- prev:xa, cur:b, i:1
- prev:xab, cur:c, i:2
- 'xabc'
And reduceRight()
iterates from right to left:
- > ['a', 'b', 'c'].reduceRight(printArgs)
- prev:c, cur:b, i:1
- prev:cb, cur:a, i:0
- 'cba'
- > ['a', 'b', 'c'].reduceRight(printArgs, 'x')
- prev:x, cur:c, i:2
- prev:xc, cur:b, i:1
- prev:xcb, cur:a, i:0
- 'xcba'
Pitfall: Array-Like Objects
Some objects in JavaScript look like an array, but they aren’t one. That usually means that they have indexed access and a length
property, but none of the array methods. Examples include the special variable arguments
, DOM node lists, and strings. Array-Like Objects and Generic Methods gives tips for working with array-like objects.
Best Practices: Iterating over Arrays
To iterate over an array arr
, you have two options:
- A simple
for
loop (see for):
for
(
var
i
=
0
;
i
<
arr
.
length
;
i
++
)
{
console
.
log
(
arr
[
i
]);
}
- One of the array iteration methods (see Iteration (Nondestructive)). For example,
forEach()
:
arr
.
forEach
(
function
(
elem
)
{
console
.
log
(
elem
);
});
Do not use the for-in
loop (see for-in) to iterate over arrays. It iterates over indices, not over values. And it includes the keys of normal properties while doing so, including inherited ones.