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

  1. > var arr = [ 'a', 'b', 'c' ]; // array literal
  2. > arr[0] // get element 0
  3. 'a'
  4. > arr[0] = 'x'; // set element 0
  5. > arr
  6. [ 'x', 'b', 'c' ]

We can use the array property length (see length) to remove and append elements:

  1. > var arr = [ 'a', 'b', 'c' ];
  2. > arr.length
  3. 3
  4. > arr.length = 2; // remove an element
  5. > arr
  6. [ 'a', 'b' ]
  7. > arr[arr.length] = 'd'; // append an element
  8. > arr
  9. [ 'a', 'b', 'd' ]

The array method push() provides another way of appending an element:

  1. > var arr = [ 'a', 'b' ];
  2. > arr.push('d')
  3. 3
  4. > arr
  5. [ '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:

  1. > var arr = [];
  2. > arr[0] = 'a';
  3. 'a'
  4. > arr[2] = 'b';
  5. 'b'
  6. > arr
  7. [ '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:

  1. > var arr = [ 'a', 'b' ];
  2. > arr.foo = 123;
  3. > arr
  4. [ 'a', 'b' ]
  5. > arr.foo
  6. 123

Creating Arrays

You create an array via an array literal:

  1. var myArray = [ 'a', 'b', 'c' ];

Trailing commas in arrays are ignored:

  1. > [ 'a', 'b' ].length
  2. 2
  3. > [ 'a', 'b', ].length
  4. 2
  5. > [ 'a', 'b', ,].length // hole + trailing comma
  6. 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

An empty array with a given length has only holes in it! Thus, it rarely makes sense to use this version of the constructor:

  1. > var arr = new Array(2);
  2. > arr.length
  3. 2
  4. > arr // two holes plus trailing comma (ignored!)
  5. [ , ,]

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:

  1. // The same as ['a', 'b', 'c']:
  2. 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:

  1. > new Array(2) // alas, not [ 2 ]
  2. [ , ,]
  3.  
  4. > new Array(5.7) // alas, not [ 5.7 ]
  5. RangeError: Invalid array length
  6.  
  7. > new Array('abc') // ok
  8. [ '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):

  1. // Create the Tic-tac-toe board
  2. var rows = [];
  3. for (var rowCount=0; rowCount < 3; rowCount++) {
  4. rows[rowCount] = [];
  5. for (var colCount=0; colCount < 3; colCount++) {
  6. rows[rowCount][colCount] = '.';
  7. }
  8. }
  9.  
  10. // Set an X in the upper right corner
  11. rows[0][2] = 'X'; // [row][column]
  12.  
  13. // Print the board
  14. rows.forEach(function (row) {
  15. console.log(row.join(' '));
  16. });

Here is the output:

  1. . . X
  2. . . .
  3. . . .

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:

  1. 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:

  1. > var arr = [];
  2.  
  3. > arr[-1] = 'a';
  4. > arr
  5. []
  6. > arr['-1']
  7. 'a'
  8.  
  9. > arr[4294967296] = 'b';
  10. > arr
  11. []
  12. > arr['4294967296']
  13. '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:

  1. > var arr = [ 'a', , 'b' ];
  2. > 0 in arr
  3. true
  4. > 1 in arr
  5. false
  6. > 10 in arr
  7. 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):

  1. > var arr = [ 'a', 'b' ];
  2. > arr.length
  3. 2
  4. > delete arr[1] // does not update length
  5. true
  6. > arr
  7. [ 'a', ]
  8. > arr.length
  9. 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:

  1. > var arr = ['a', 'b', 'c', 'd'];
  2. > arr.splice(1, 2) // returns what has been removed
  3. [ 'b', 'c' ]
  4. > arr
  5. [ '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 if ToString(ToUint32(P)) is equal to P and ToUint32(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:

  1. > var arr = ['a', 'b'];
  2. > arr['0']
  3. 'a'
  4. > arr[0]
  5. '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:

  1. > ToUint32('xyz')
  2. 0
  3. > ToUint32('?@#!')
  4. 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:

  1. > ToUint32('-1')
  2. 4294967295
  3. > Math.pow(2, 32)
  4. 4294967296
  5. > ToUint32('4294967296')
  6. 0

Third, stringified noninteger numbers are converted to integers, which are, again, different:

  1. > ToUint32('1.371')
  2. 1

Note that the specification also enforces that array indices don’t have exponents:

  1. > ToUint32('1e3')
  2. 1000

And that they don’t have leading zeros:

  1. > var arr = ['a', 'b'];
  2. > arr['0'] // array index
  3. 'a'
  4. > arr['00'] // normal property
  5. undefined

length

The basic function of the length property is to track the highest index in an array:

  1. > [ 'a', 'b' ].length
  2. 2
  3. > [ 'a', , 'b' ].length
  4. 3

Thus, length does not count the number of elements, so you’d have to write your own function for doing so. For example:

  1. function countElements(arr) {
  2. var elemCount = 0;
  3. arr.forEach(function () {
  4. elemCount++;
  5. });
  6. return elemCount;
  7. }

To count elements (nonholes), we have used the fact that forEach skips holes.Here is the interaction:

  1. > countElements([ 'a', 'b' ])
  2. 2
  3. > countElements([ 'a', , 'b' ])
  4. 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:

  1. > var arr = [ 'a', 'b' ];
  2. > arr.length = 3;
  3. > arr // one hole at the end
  4. [ '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:

  1. > countElements(arr)
  2. 2

However, the length property does act as a pointer indicating where to insert new elements. For example:

  1. > arr.push('c')
  2. 4
  3. > arr
  4. [ 'a', 'b', , 'c' ]

Thus, setting the initial length of an array via the Array constructor creates an array that is completely empty:

  1. > var arr = new Array(2);
  2. > arr.length
  3. 2
  4. > countElements(arr)
  5. 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:

  1. > var arr = [ 'a', 'b', 'c' ];
  2. > 1 in arr
  3. true
  4. > arr[1]
  5. 'b'
  6.  
  7. > arr.length = 1;
  8. > arr
  9. [ 'a' ]
  10. > 1 in arr
  11. false
  12. > arr[1]
  13. 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:

  1. function clearArray(arr) {
  2. arr.length = 0;
  3. }

Here’s the interaction:

  1. > var arr = [ 'a', 'b', 'c' ];
  2. > clearArray(arr)
  3. > arr
  4. []

Note, however, that this approach can be slow, because each array element is explicitly deleted. Ironically, creating a new empty array is often faster:

  1. 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:

  1. > var a1 = [1, 2, 3];
  2. > var a2 = a1;
  3. > a1.length = 0;
  4.  
  5. > a1
  6. []
  7. > a2
  8. []

In contrast, assigning an empty array doesn’t:

  1. > var a1 = [1, 2, 3];
  2. > var a2 = a1;
  3. > a1 = [];
  4.  
  5. > a1
  6. []
  7. > a2
  8. [ 1, 2, 3 ]

The Maximum Length

The maximum array length is 232−1:

  1. > var arr1 = new Array(Math.pow(2, 32)); // not ok
  2. RangeError: Invalid array length
  3.  
  4. > var arr2 = new Array(Math.pow(2, 32)-1); // ok
  5. > arr2.push('x');
  6. 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:

  1. > var arr = [];
  2. > arr[0] = 'a';
  3. > arr[2] = 'c';
  4. > 1 in arr // hole at index 1
  5. false

You can also create holes by omitting values in array literals:

  1. > var arr = ['a',,'c'];
  2. > 1 in arr // hole at index 1
  3. false

Warning

You need two trailing commas to create a trailing hole, because the last comma is always ignored:

  1. > [ 'a', ].length
  2. 1
  3. > [ 'a', ,].length
  4. 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:

  1. var sparse = [ , , 'c' ];
  2. var dense = [ undefined, undefined, 'c' ];

A hole is almost like having the element undefined at the same index. Both arrays have the same length:

  1. > sparse.length
  2. 3
  3. > dense.length
  4. 3

But the sparse array does not have an element at index 0:

  1. > 0 in sparse
  2. false
  3. > 0 in dense
  4. true

Iteration via for is the same for both arrays:

  1. > for (var i=0; i<sparse.length; i++) console.log(sparse[i]);
  2. undefined
  3. undefined
  4. c
  5. > for (var i=0; i<dense.length; i++) console.log(dense[i]);
  6. undefined
  7. undefined
  8. c

Iteration via forEach skips the holes, but not the undefined elements:

  1. > sparse.forEach(function (x) { console.log(x) });
  2. c
  3. > dense.forEach(function (x) { console.log(x) });
  4. undefined
  5. undefined
  6. 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:

  1. > ['a',, 'b'].forEach(function (x,i) { console.log(i+'.'+x) })
  2. 0.a
  3. 2.b

every() also skips holes (similarly: some()):

  1. > ['a',, 'b'].every(function (x) { return typeof x === 'string' })
  2. true

map() skips, but preserves holes:

  1. > ['a',, 'b'].map(function (x,i) { return i+'.'+x })
  2. [ '0.a', , '2.b' ]

filter() eliminates holes:

  1. > ['a',, 'b'].filter(function (x) { return true })
  2. [ 'a', 'b' ]

Other array methods

join() converts holes, undefineds, and nulls to empty strings:

  1. > ['a',, 'b'].join('-')
  2. 'a--b'
  3. > [ 'a', undefined, 'b' ].join('-')
  4. 'a--b'

sort() preserves holes while sorting:

  1. > ['a',, 'b'].sort() // length of result is 3
  2. [ 'a', 'b', , ]

The for-in loop

The for-in loop correctly lists property keys (which are a superset of array indices):

  1. > for (var key in ['a',, 'b']) { console.log(key) }
  2. 0
  3. 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:

  1. > function f() { return [].slice.call(arguments) }
  2. > f.apply(null, [ , , ,])
  3. [ undefined, undefined, undefined ]

That means that we can use apply() to create an array with undefineds:

  1. > Array.apply(null, Array(3))
  2. [ undefined, undefined, undefined ]

Warning

apply() translates holes to undefineds 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]:

  1. > Array.apply(null, [2])
  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:

  1. > ['a',, 'b'].filter(function (x) { return true })
  2. [ 'a', 'b' ]

Use a custom function to convert holes to undefineds in arbitrary arrays:

  1. function convertHolesToUndefineds(arr) {
  2. var result = [];
  3. for (var i=0; i < arr.length; i++) {
  4. result[i] = arr[i];
  5. }
  6. return result;
  7. }

Using the function:

  1. > convertHolesToUndefineds(['a',, 'b'])
  2. [ 'a', undefined, 'b' ]

Array Constructor Method

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:
  1. > var arr = [ 'a', 'b' ];
  2. > arr.shift()
  3. 'a'
  4. > arr
  5. [ 'b' ]
  • Array.prototype.unshift(elem1?, elem2?, …)
  • Prepends the given elements to the array. It returns the new length:
  1. > var arr = [ 'c', 'd' ];
  2. > arr.unshift('a', 'b')
  3. 4
  4. > arr
  5. [ 'a', 'b', 'c', 'd' ]
  • Array.prototype.pop()
  • Removes the last element of the array and returns it:
  1. > var arr = [ 'a', 'b' ];
  2. > arr.pop()
  3. 'b'
  4. > arr
  5. [ 'a' ]
  • Array.prototype.push(elem1?, elem2?, …)
  • Adds the given elements to the end of the array. It returns the new length:
  1. > var arr = [ 'a', 'b' ];
  2. > arr.push('c', 'd')
  3. 4
  4. > arr
  5. [ 'a', 'b', 'c', 'd' ]

apply() (see Function.prototype.apply(thisValue, argArray)) enables you to destructively append an array arr2 to another array arr1:

  1. > var arr1 = [ 'a', 'b' ];
  2. > var arr2 = [ 'c', 'd' ];
  3.  
  4. > Array.prototype.push.apply(arr1, arr2)
  5. 4
  6. > arr1
  7. [ 'a', 'b', 'c', 'd' ]
  • Array.prototype.splice(start, deleteCount?, elem1?, elem2?, …)
  • Starting at start, removes deleteCount elements and inserts the elements given. In other words, you are replacing the deleteCount elements at position start with elem1, elem2, and so on. The method returns the elements that have been removed:
  1. > var arr = [ 'a', 'b', 'c', 'd' ];
  2. > arr.splice(1, 2, 'X');
  3. [ 'b', 'c' ]
  4. > arr
  5. [ '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 index start are removed.

In this example, we remove all elements after and including the second-to-last index:

  1. > var arr = [ 'a', 'b', 'c', 'd' ];
  2. > arr.splice(-2)
  3. [ 'c', 'd' ]
  4. > arr
  5. [ '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:
  1. > var arr = [ 'a', 'b', 'c' ];
  2. > arr.reverse()
  3. [ 'c', 'b', 'a' ]
  4. > arr // reversing happened in place
  5. [ 'c', 'b', 'a' ]
  • Array.prototype.sort(compareFunction?)
  • Sorts the array and returns it:
  1. > var arr = ['banana', 'apple', 'pear', 'orange'];
  2. > arr.sort()
  3. [ 'apple', 'banana', 'orange', 'pear' ]
  4. > arr // sorting happened in place
  5. [ '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. > [-1, -20, 7, 50].sort()
  2. [ -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:

  1. function compareFunction(a, b)

This function compares a and b and returns:

  • An integer less than zero (e.g., -1) if a is less than b
  • Zero if a is equal to b
  • An integer greater than zero (e.g., 1) if a is greater than b

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:

  1. function compareCanonically(a, b) {
  2. if (a < b) {
  3. return -1;
  4. } else if (a > b) {
  5. return 1;
  6. } else {
  7. return 0;
  8. }
  9. }

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:

  1. function compareCanonically(a, b) {
  2. return a < b ? -1 : (a > b ? 1 : 0);
  3. }

Using the function:

  1. > [-1, -20, 7, 50].sort(compareCanonically)
  2. [ -20, -1, 7, 50 ]

Comparing Strings

For strings, you can use String.prototype.localeCompare (see Comparing Strings):

  1. > ['c', 'a', 'b'].sort(function (a,b) { return a.localeCompare(b) })
  2. [ 'a', 'b', 'c' ]

Comparing Objects

The parameter compareFunction is also useful for sorting objects:

  1. var arr = [
  2. { name: 'Tarzan' },
  3. { name: 'Cheeta' },
  4. { name: 'Jane' } ];
  5.  
  6. function compareNames(a,b) {
  7. return a.name.localeCompare(b.name);
  8. }

With compareNames as the compare function, arr is sorted by name:

  1. > arr.sort(compareNames)
  2. [ { name: 'Cheeta' },
  3. { name: 'Jane' },
  4. { 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):
  1. > var arr = [ 'a', 'b' ];
  2. > arr.concat('c', ['d', 'e'])
  3. [ 'a', 'b', 'c', 'd', 'e' ]

The array that concat() is invoked on is not changed:

  1. > arr
  2. [ 'a', 'b' ]
  • Array.prototype.slice(begin?, end?)
  • Copies array elements into a new array, starting at begin, until and excluding the element at end:
  1. > [ 'a', 'b', 'c', 'd' ].slice(1, 3)
  2. [ 'b', 'c' ]

If end is missing, the array length is used:

  1. > [ 'a', 'b', 'c', 'd' ].slice(1)
  2. [ 'b', 'c', 'd' ]

If both indices are missing, the array is copied:

  1. > [ 'a', 'b', 'c', 'd' ].slice()
  2. [ '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:

  1. > [ 'a', 'b', 'c', 'd' ].slice(1, -1)
  2. [ 'b', 'c' ]
  3. > [ 'a', 'b', 'c', 'd' ].slice(-2)
  4. [ 'c', 'd' ]
  • Array.prototype.join(separator?)
  • Creates a string by applying toString() to all array elements and putting the string in separator between the results. If separator is omitted, ',' is used:
  1. > [3, 4, 5].join('-')
  2. '3-4-5'
  3. > [3, 4, 5].join()
  4. '3,4,5'
  5. > [3, 4, 5].join('')
  6. '345'

join() converts undefined and null to empty strings:

  1. > [undefined, null].join('#')
  2. '#'

Holes in arrays are also converted to empty strings:

  1. > ['a',, 'b'].join('-')
  2. '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 at startIndex. It returns the index of the first occurrence or –1 if nothing is found. If startIndex is negative, the array length is added to it; if it is missing, the whole array is searched:
  1. > [ 3, 1, 17, 1, 4 ].indexOf(1)
  2. 1
  3. > [ 3, 1, 17, 1, 4 ].indexOf(1, 2)
  4. 3

Strict equality (see Equality Operators: === Versus ==) is used for the search, which means that indexOf() can’t find NaN:

  1. > [NaN].indexOf(NaN)
  2. -1
  • Array.prototype.lastIndexOf(searchElement, startIndex?)
  • Searches the array for searchElement, starting at startIndex, backward. It returns the index of the first occurrence or –1 if nothing is found. If startIndex 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:
  1. > [ 3, 1, 17, 1, 4 ].lastIndexOf(1)
  2. 3
  3. > [ 3, 1, 17, 1, 4 ].lastIndexOf(1, -3)
  4. 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:

  1. 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:
  1. 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 of this inside callback.

And now for the examination methods whose signatures I have just described:

  • Array.prototype.forEach(callback, thisValue?)
  • Iterates over the elements of an array:
  1. var arr = [ 'apple', 'pear', 'orange' ];
  2. arr.forEach(function (elem) {
  3. console.log(elem);
  4. });
  • Array.prototype.every(callback, thisValue?)
  • Returns true if the callback returns true for every element. It stops iteration as soon as the callback returns false.Note that not returning a value leads to an implicit return of undefined, which every() interprets as false.every() works like the universal quantifier (“for all”).

This example checks whether every number in the array is even:

  1. > function isEven(x) { return x % 2 === 0 }
  2. > [ 2, 4, 6 ].every(isEven)
  3. true
  4. > [ 2, 3, 4 ].every(isEven)
  5. false

If the array is empty, the result is true (and callback is not called):

  1. > [].every(function () { throw new Error() })
  2. true
  • Array.prototype.some(callback, thisValue?)
  • Returns true if the callback returns true for at least one element. It stops iteration as soon as the callback returns true.Note that not returning a value leads to an implicit return of undefined, which some interprets as false.some() works like the existential quantifier (“there exists”).

This example checks whether there is an even number in the array:

  1. > function isEven(x) { return x % 2 === 0 }
  2. > [ 1, 3, 5 ].some(isEven)
  3. false
  4. > [ 1, 2, 3 ].some(isEven)
  5. true

If the array is empty, the result is false (and callback is not called):

  1. > [].some(function () { throw new Error() })
  2. 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():

  1. function breakAtEmptyString(strArr) {
  2. strArr.some(function (elem) {
  3. if (elem.length === 0) {
  4. return true; // break
  5. }
  6. console.log(elem);
  7. // implicit: return undefined (interpreted as false)
  8. });
  9. }

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:

  1. 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. > [ 1, 2, 3 ].map(function (x) { return 2 * x })
  2. [ 2, 4, 6 ]
  • Array.prototype.filter(callback, thisValue?)
  • The output array contains only those input elements for which callback returns true. For example:
  1. > [ 1, 0, 3, 0 ].filter(function (x) { return x !== 0 })
  2. [ 1, 3 ]

Reduction Methods

For reducing, the callback has a different signature:

  1. 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. Then previousValue is initialValue, and currentElement is the first array element (reduceRight: the last array element).
  • No explicit initialValue has been provided. Then previousValue is the first array element, and currentElement 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:
  1. function add(prev, cur) {
  2. return prev + cur;
  3. }
  4. console.log([10, 3, -1].reduce(add)); // 12

If you invoke reduce on an array with a single element, that element is returned:

  1. > [7].reduce(add)
  2. 7

If you invoke reduce on an empty array, you must specify initialValue, otherwise you get an exception:

  1. > [].reduce(add)
  2. TypeError: Reduce of empty array with no initial value
  3. > [].reduce(add, 123)
  4. 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:

  1. function printArgs(prev, cur, i) {
  2. console.log('prev:'+prev+', cur:'+cur+', i:'+i);
  3. return prev + cur;
  4. }

As expected, reduce() iterates from left to right:

  1. > ['a', 'b', 'c'].reduce(printArgs)
  2. prev:a, cur:b, i:1
  3. prev:ab, cur:c, i:2
  4. 'abc'
  5. > ['a', 'b', 'c'].reduce(printArgs, 'x')
  6. prev:x, cur:a, i:0
  7. prev:xa, cur:b, i:1
  8. prev:xab, cur:c, i:2
  9. 'xabc'

And reduceRight() iterates from right to left:

  1. > ['a', 'b', 'c'].reduceRight(printArgs)
  2. prev:c, cur:b, i:1
  3. prev:cb, cur:a, i:0
  4. 'cba'
  5. > ['a', 'b', 'c'].reduceRight(printArgs, 'x')
  6. prev:x, cur:c, i:2
  7. prev:xc, cur:b, i:1
  8. prev:xcb, cur:a, i:0
  9. '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):
  1. for (var i=0; i<arr.length; i++) {
  2. console.log(arr[i]);
  3. }
  1. arr.forEach(function (elem) {
  2. console.log(elem);
  3. });

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.