0% found this document useful (0 votes)
4 views46 pages

31 Arrays ('Array') - JavaScript For Impatient Programmers (ES2022 Edition)

The document provides a comprehensive overview of Arrays in JavaScript, detailing their creation, manipulation, and various methods available for operations such as adding, removing, and iterating over elements. It covers both basic and advanced features, including the use of negative indices, multidimensional arrays, and array-like objects. Additionally, it explains the distinction between fixed-layout and sequence arrays, emphasizing their flexibility in different programming contexts.

Uploaded by

aa4463
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views46 pages

31 Arrays ('Array') - JavaScript For Impatient Programmers (ES2022 Edition)

The document provides a comprehensive overview of Arrays in JavaScript, detailing their creation, manipulation, and various methods available for operations such as adding, removing, and iterating over elements. It covers both basic and advanced features, including the use of negative indices, multidimensional arrays, and array-like objects. Additionally, it explains the distinction between fixed-layout and sequence arrays, emphasizing their flexibility in different programming contexts.

Uploaded by

aa4463
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 46

5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

JavaScript for impatient programmers (ES2022 edition)


Please support this book: buy it or donate

(Ad, please don’t block.)


Redesign the way
you jam with FigJam
AI.

ads via Carbon

31 Arrays (Array)

31.1 Cheat sheet: Arrays


31.1.1 Using Arrays
31.1.2 Array methods
31.2 The two ways of using Arrays in JavaScript
31.3 Basic Array operations
31.3.1 Creating, reading, writing Arrays
31.3.2 The .length of an Array
31.3.3 Referring to elements via negative indices
31.3.4 Clearing Arrays
31.3.5 Spreading into Array literals [ES6]
31.3.6 Arrays: listing indices and entries [ES6]
31.3.7 Is a value an Array?
31.4 for-of and Arrays [ES6]
31.4.1 for-of: iterating over elements
31.4.2 for-of: iterating over indices
31.4.3 for-of: iterating over [index, element] pairs
31.5 Array-like objects
31.6 Converting iterables and Array-like values to Arrays
31.6.1 Converting iterables to Arrays via spreading (...)
31.6.2 Converting iterables and Array-like objects to Arrays via Array.from()
31.7 Creating and filling Arrays with arbitrary lengths
31.7.1 Do we need to create an empty Array that we’ll fill completely later on?
31.7.2 Do we need to create an Array filled with a primitive value?
31.7.3 Do we need to create an Array filled with objects?
31.7.4 Do we need to create a range of integers?
31.7.5 Use a Typed Array if the elements are all integers or all floats

https://exploringjs.com/impatient-js/ch_arrays.html 1/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

31.8 Multidimensional Arrays


31.9 More Array features (advanced)
31.9.1 Array indices are (slightly special) property keys
31.9.2 Arrays are dictionaries and can have holes
31.10 Adding and removing elements (destructively and non-destructively)
31.10.1 Prepending elements and Arrays
31.10.2 Appending elements and Arrays
31.10.3 Removing elements
31.11 Methods: iteration and transformation (.find(), .map(), .filter(), etc.)
31.11.1 Callbacks for iteration and transformation methods
31.11.2 Searching elements: .find(), .findIndex()
31.11.3 .map(): copy while giving elements new values
31.11.4 .flatMap(): mapping to zero or more values
31.11.5 .filter(): only keep some of the elements
31.11.6 .reduce(): deriving a value from an Array (advanced)
31.12 .sort(): sorting Arrays
31.12.1 Customizing the sort order
31.12.2 Sorting numbers
31.12.3 Sorting objects
31.13 Quick reference: Array
31.13.1 new Array()
31.13.2 Static methods of Array
31.13.3 Methods of Array.prototype
31.13.4 Sources

31.1 Cheat sheet: Arrays

JavaScript Arrays are a very flexible data structure and used as lists, stacks, queues, tuples
(e.g. pairs), and more.

Some Array-related operations destructively change Arrays. Others non-destructively


produce new Arrays with the changes applied to a copy of the original content.

31.1.1 Using Arrays

Creating an Array, reading and writing elements:

// Creating an Array
const arr = ['a', 'b', 'c']; // Array literal
assert.deepEqual(

https://exploringjs.com/impatient-js/ch_arrays.html 2/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

arr,
[ // Array literal
'a',
'b',
'c', // trailing commas are ignored
]
);

// Reading elements
assert.equal(
arr[0], 'a' // negative indices don’t work
);
assert.equal(
arr.at(-1), 'c' // negative indices work
);

// Writing an element
arr[0] = 'x';
assert.deepEqual(
arr, ['x', 'b', 'c']
);

The length of an Array:

const arr = ['a', 'b', 'c'];


assert.equal(
arr.length, 3 // number of elements
);
arr.length = 1; // removing elements
assert.deepEqual(
arr, ['a']
);
arr[arr.length] = 'b'; // adding an element
assert.deepEqual(
arr, ['a', 'b']
);

Adding elements destructively via .push():

const arr = ['a', 'b'];

arr.push('c'); // adding an element


assert.deepEqual(
arr, ['a', 'b', 'c']

https://exploringjs.com/impatient-js/ch_arrays.html 3/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

);

// Pushing Arrays (used as arguments via spreading (...)):


arr.push(...['d', 'e']);
assert.deepEqual(
arr, ['a', 'b', 'c', 'd', 'e']
);

Adding elements non-destructively via spreading (...):

const arr1 = ['a', 'b'];


const arr2 = ['c'];
assert.deepEqual(
[...arr1, ...arr2, 'd', 'e'],
['a', 'b', 'c', 'd', 'e']
);

Clearing Arrays (removing all elements):

// Destructive – affects everyone referring to the Array:


const arr1 = ['a', 'b', 'c'];
arr1.length = 0;
assert.deepEqual(
arr1, []
);

// Non-destructive – does not affect others referring to the Array:


let arr2 = ['a', 'b', 'c'];
arr2 = [];
assert.deepEqual(
arr2, []
);

Looping over elements:

const arr = ['a', 'b', 'c'];


for (const value of arr) {
console.log(value);
}

// Output:
// 'a'
// 'b'
// 'c'

https://exploringjs.com/impatient-js/ch_arrays.html 4/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

Looping over index-value pairs:

const arr = ['a', 'b', 'c'];


for (const [index, value] of arr.entries()) {
console.log(index, value);
}

// Output:
// 0, 'a'
// 1, 'b'
// 2, 'c'

Creating and filling Arrays when we can’t use Array literals (e.g. because we don’t know
their lengths in advance or they are too large):

const four = 4;

// Empty Array that we’ll fill later


assert.deepEqual(
new Array(four),
[ , , , ,] // four holes; last comma is ignored
);

// An Array filled with a primitive value


assert.deepEqual(
new Array(four).fill(0),
[0, 0, 0, 0]
);

// An Array filled with objects


// Why not .fill()? We’d get single object, shared multiple times.
assert.deepEqual(
Array.from({length: four}, () => ({})),
[{}, {}, {}, {}]
);

// A range of integers
assert.deepEqual(
Array.from({length: four}, (_, i) => i),
[0, 1, 2, 3]
);

31.1.2 Array methods

https://exploringjs.com/impatient-js/ch_arrays.html 5/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

This section gives a brief overview of the Array API. There is a more comprehensive quick
reference at the end of this chapter.

Deriving a new Array from an existing Array:

> ['■','●','▲'].slice(1, 3)
['●','▲']
> ['■','●','■'].filter(x => x==='■')
['■','■']

> ['▲','●'].map(x => x+x)


['▲▲','●●']
> ['▲','●'].flatMap(x => [x,x])
['▲','▲','●','●']

Removing an Array element at a given index:

// .filter(): remove non-destructively


const arr1 = ['■','●','▲'];
assert.deepEqual(
arr1.filter((_, index) => index !== 1),
['■','▲']
);
assert.deepEqual(
arr1, ['■','●','▲'] // unchanged
);

// .splice(): remove destructively


const arr2 = ['■','●','▲'];
arr2.splice(1, 1); // start at 1, delete 1 element
assert.deepEqual(
arr2, ['■','▲'] // changed
);

Computing a summary of an Array:

> ['■','●','▲'].some(x => x==='●')


true
> ['■','●','▲'].every(x => x==='●')
false

> ['■','●','▲'].join('-')
'■-●-▲'

https://exploringjs.com/impatient-js/ch_arrays.html 6/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

> ['■','▲'].reduce((result,x) => result+x, '●')


'●■▲'
> ['■','▲'].reduceRight((result,x) => result+x, '●')
'●▲■'

Reversing and filling:

// .reverse() changes and returns `arr`


const arr = ['■','●','▲'];
assert.deepEqual(
arr.reverse(), arr
);
// `arr` was changed:
assert.deepEqual(
arr, ['▲','●','■']
);

// .fill() works the same way:


assert.deepEqual(
['■','●','▲'].fill('●'),
['●','●','●']
);

.sort() also modifies an Array and returns it:

// By default, string representations of the Array elements


// are sorted lexicographically:
assert.deepEqual(
[200, 3, 10].sort(),
[10, 200, 3]
);

// Sorting can be customized via a callback:


assert.deepEqual(
[200, 3, 10].sort((a,b) => a - b), // sort numerically
[ 3, 10, 200 ]
);

Finding Array elements:

> ['■','●','■'].includes('■')
true
> ['■','●','■'].indexOf('■')
0

https://exploringjs.com/impatient-js/ch_arrays.html 7/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

> ['■','●','■'].lastIndexOf('■')
2
> ['■','●','■'].find(x => x==='■')
'■'
> ['■','●','■'].findIndex(x => x==='■')
0

Adding or removing an element at the start or the end:

// Adding and removing at the start


const arr1 = ['■','●'];
arr1.unshift('▲');
assert.deepEqual(
arr1, ['▲','■','●']
);
arr1.shift();
assert.deepEqual(
arr1, ['■','●']
);

// Adding and removing at the end


const arr2 = ['■','●'];
arr2.push('▲');
assert.deepEqual(
arr2, ['■','●','▲']
);
arr2.pop();
assert.deepEqual(
arr2, ['■','●']
);

31.2 The two ways of using Arrays in JavaScript

There are two ways of using Arrays in JavaScript:

Fixed-layout Arrays: Used this way, Arrays have a fixed number of indexed
elements. Each of those elements can have a different type.
Sequence Arrays: Used this way, Arrays have a variable number of indexed
elements. Each of those elements has the same type.

In practice, these two ways are often mixed.

Notably, sequence Arrays are so flexible that we can use them as (traditional) arrays,
stacks, and queues. We’ll see how later.
https://exploringjs.com/impatient-js/ch_arrays.html 8/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

31.3 Basic Array operations

31.3.1 Creating, reading, writing Arrays

The best way to create an Array is via an Array literal:

const arr = ['a', 'b', 'c'];

The Array literal starts and ends with square brackets []. It creates an Array with three
elements: 'a', 'b', and 'c'.

Trailing commas are allowed and ignored in Array literals:

const arr = [
'a',
'b',
'c',
];

To read an Array element, we put an index in square brackets (indices start at zero):

const arr = ['a', 'b', 'c'];


assert.equal(arr[0], 'a');

To change an Array element, we assign to an Array with an index:

const arr = ['a', 'b', 'c'];


arr[0] = 'x';
assert.deepEqual(arr, ['x', 'b', 'c']);

The range of Array indices is 32 bits (excluding the maximum length): [0, 232−1)

31.3.2 The .length of an Array

Every Array has a property .length that can be used to both read and change(!) the
number of elements in an Array.

The length of an Array is always the highest index plus one:

> const arr = ['a', 'b'];


> arr.length
2

If we write to the Array at the index of the length, we append an element:

https://exploringjs.com/impatient-js/ch_arrays.html 9/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

> arr[arr.length] = 'c';


> arr
[ 'a', 'b', 'c' ]
> arr.length
3

Another way of (destructively) appending an element is via the Array method .push():

> arr.push('d');
> arr
[ 'a', 'b', 'c', 'd' ]

If we set .length, we are pruning the Array by removing elements:

> arr.length = 1;
> arr
[ 'a' ]

Exercise: Removing empty lines via .push()

exercises/arrays/remove_empty_lines_push_test.mjs

31.3.3 Referring to elements via negative indices

Several Array methods support negative indices. If an index is negative, it is added to the
length of an Array to produce a usable index. Therefore, the following two invocations of
.slice() are equivalent: They both copy arr starting at the last element.

> const arr = ['a', 'b', 'c'];


> arr.slice(-1)
[ 'c' ]
> arr.slice(arr.length - 1)
[ 'c' ]

31.3.3.1 .at(): reading single elements (supports negative indices) [ES2022]

The Array method .at() returns the element at a given index. It supports positive and
negative indices (-1 refers to the last element, -2 refers to the second-last element, etc.):

> ['a', 'b', 'c'].at(0)


'a'
> ['a', 'b', 'c'].at(-1)
'c'

https://exploringjs.com/impatient-js/ch_arrays.html 10/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

In contrast, the bracket operator [] does not support negative indices (and can’t be
changed because that would break existing code). It interprets them as keys of non-
element properties:

const arr = ['a', 'b', 'c'];

arr[-1] = 'non-element property';


// The Array elements didn’t change:
assert.deepEqual(
Array.from(arr), // copy just the Array elements
['a', 'b', 'c']
);

assert.equal(
arr[-1], 'non-element property'
);

31.3.4 Clearing Arrays

To clear (empty) an Array, we can either set its .length to zero:

const arr = ['a', 'b', 'c'];


arr.length = 0;
assert.deepEqual(arr, []);

or we can assign a new empty Array to the variable storing the Array:

let arr = ['a', 'b', 'c'];


arr = [];
assert.deepEqual(arr, []);

The latter approach has the advantage of not affecting other locations that point to the
same Array. If, however, we do want to reset a shared Array for everyone, then we need
the former approach.

31.3.5 Spreading into Array literals [ES6]

Inside an Array literal, a spread element consists of three dots (...) followed by an
expression. It results in the expression being evaluated and then iterated over. Each
iterated value becomes an additional Array element – for example:

> const iterable = ['b', 'c'];


> ['a', ...iterable, 'd']
[ 'a', 'b', 'c', 'd' ]
https://exploringjs.com/impatient-js/ch_arrays.html 11/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

That means that we can use spreading to create a copy of an Array and to convert an
iterable to an Array:

const original = ['a', 'b', 'c'];

const copy = [...original];

const iterable = original.keys();


assert.deepEqual(
[...iterable], [0, 1, 2]
);

However, for both previous use cases, I find Array.from() more self-descriptive and prefer
it:

const copy2 = Array.from(original);

assert.deepEqual(
Array.from(original.keys()), [0, 1, 2]
);

Spreading is also convenient for concatenating Arrays (and other iterables) into Arrays:

const arr1 = ['a', 'b'];


const arr2 = ['c', 'd'];

const concatenated = [...arr1, ...arr2, 'e'];


assert.deepEqual(
concatenated,
['a', 'b', 'c', 'd', 'e']);

Due to spreading using iteration, it only works if the value is iterable:

> [...'abc'] // strings are iterable


[ 'a', 'b', 'c' ]
> [...123]
TypeError: 123 is not iterable
> [...undefined]
TypeError: undefined is not iterable

Spreading and Array.from() produce shallow copies

Copying Arrays via spreading or via Array.from() is shallow: We get new entries in a
new Array, but the values are shared with the original Array. The consequences of

https://exploringjs.com/impatient-js/ch_arrays.html 12/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

shallow copying are demonstrated in §28.4 “Spreading into object literals (...)
[ES2018]”.

31.3.6 Arrays: listing indices and entries [ES6]

Method .keys() lists the indices of an Array:

const arr = ['a', 'b'];


assert.deepEqual(
Array.from(arr.keys()), // (A)
[0, 1]);

.keys() returns an iterable. In line A, we convert that iterable to an Array.

Listing Array indices is different from listing properties. The former produces numbers;
the latter produces stringified numbers (in addition to non-index property keys):

const arr = ['a', 'b'];


arr.prop = true;

assert.deepEqual(
Object.keys(arr),
['0', '1', 'prop']);

Method .entries() lists the contents of an Array as [index, element] pairs:

const arr = ['a', 'b'];


assert.deepEqual(
Array.from(arr.entries()),
[[0, 'a'], [1, 'b']]);

31.3.7 Is a value an Array?

Following are two ways of checking if a value is an Array:

> [] instanceof Array


true
> Array.isArray([])
true

instanceof is usually fine. We need Array.isArray() if a value may come from another
realm. Roughly, a realm is an instance of JavaScript’s global scope. Some realms are
isolated from each other (e.g., Web Workers in browsers), but there are also realms
between which we can move data – for example, same-origin iframes in browsers. x

https://exploringjs.com/impatient-js/ch_arrays.html 13/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

instanceof Arraychecks the prototype chain of x and therefore returns false if x is an Array
from another realm.

typeof categorizes Arrays as objects:

> typeof []
'object'

31.4 for-of and Arrays [ES6]

We have already encountered the for-of loop earlier in this book. This section briefly
recaps how to use it for Arrays.

31.4.1 for-of: iterating over elements

The following for-of loop iterates over the elements of an Array:

for (const element of ['a', 'b']) {


console.log(element);
}
// Output:
// 'a'
// 'b'

31.4.2 for-of: iterating over indices

This for-of loop iterates over the indices of an Array:

for (const element of ['a', 'b'].keys()) {


console.log(element);
}
// Output:
// 0
// 1

31.4.3 for-of: iterating over [index, element] pairs

The following for-of loop iterates over [index, element] pairs. Destructuring (described
later), gives us convenient syntax for setting up index and element in the head of for-of.

for (const [index, element] of ['a', 'b'].entries()) {


console.log(index, element);
}

https://exploringjs.com/impatient-js/ch_arrays.html 14/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

// Output:
// 0, 'a'
// 1, 'b'

31.5 Array-like objects

Some operations that work with Arrays require only the bare minimum: values must only
be Array-like. An Array-like value is an object with the following properties:

.length: holds the length of the Array-like object.


[0]:
holds the element at index 0 (etc.). Note that if we use numbers as property
names, they are always coerced to strings. Therefore, [0] retrieves the value of the
property whose key is '0'.

For example, Array.from() accepts Array-like objects and converts them to Arrays:

// If we omit .length, it is interpreted as 0


assert.deepEqual(
Array.from({}),
[]);

assert.deepEqual(
Array.from({length:2, 0:'a', 1:'b'}),
[ 'a', 'b' ]);

The TypeScript interface for Array-like objects is:

interface ArrayLike<T> {
length: number;
[n: number]: T;
}

Array-like objects are relatively rare in modern JavaScript

Array-like objects used to be common before ES6; now we don’t see them very often.

31.6 Converting iterables and Array-like values to Arrays

There are two common ways of converting iterables and Array-like values to Arrays:

Spreading into Arrays


Array.from()

I prefer the latter – I find it more self-explanatory.


https://exploringjs.com/impatient-js/ch_arrays.html 15/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

31.6.1 Converting iterables to Arrays via spreading (...)

Inside an Array literal, spreading via ... converts any iterable object into a series of Array
elements. For example:

// Get an Array-like collection from a web browser’s DOM


const domCollection = document.querySelectorAll('a');

// Alas, the collection is missing many Array methods


assert.equal('map' in domCollection, false);

// Solution: convert it to an Array


const arr = [...domCollection];
assert.deepEqual(
arr.map(x => x.href),
['https://2ality.com', 'https://exploringjs.com']);

The conversion works because the DOM collection is iterable.

31.6.2 Converting iterables and Array-like objects to Arrays via Array.from()

Array.from() can be used in two modes.

31.6.2.1 Mode 1 of Array.from(): converting

The first mode has the following type signature:

.from<T>(iterable: Iterable<T> | ArrayLike<T>): T[]

Interface Iterable is shown in the chapter on synchronous iteration. Interface ArrayLike


appeared earlier in this chapter.

With a single parameter, Array.from() converts anything iterable or Array-like to an Array:

> Array.from(new Set(['a', 'b']))


[ 'a', 'b' ]
> Array.from({length: 2, 0:'a', 1:'b'})
[ 'a', 'b' ]

31.6.2.2 Mode 2 of Array.from(): converting and mapping

The second mode of Array.from() involves two parameters:

https://exploringjs.com/impatient-js/ch_arrays.html 16/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

.from<T, U>(
iterable: Iterable<T> | ArrayLike<T>,
mapFunc: (v: T, i: number) => U,
thisArg?: any)
: U[]

In this mode, Array.from() does several things:

It iterates over iterable.


It calls mapFunc with each iterated value. The optional parameter thisArg specifies a
this for mapFunc.
It applies mapFunc to each iterated value.
It collects the results in a new Array and returns it.

In other words: we are going from an iterable with elements of type T to an Array with
elements of type U.

This is an example:

> Array.from(new Set(['a', 'b']), x => x + x)


[ 'aa', 'bb' ]

31.7 Creating and filling Arrays with arbitrary lengths

The best way of creating an Array is via an Array literal. However, we can’t always use
one: The Array may be too large, we may not know its length during development, or we
may want to keep its length flexible. Then I recommend the following techniques for
creating, and possibly filling, Arrays.

31.7.1 Do we need to create an empty Array that we’ll fill completely later on?

> new Array(3)


[ , , ,]

Note that the result has three holes (empty slots) – the last comma in an Array literal is
always ignored.

31.7.2 Do we need to create an Array filled with a primitive value?

> new Array(3).fill(0)


[0, 0, 0]

https://exploringjs.com/impatient-js/ch_arrays.html 17/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

Caveat: If we use .fill() with an object, then each Array element will refer to this object
(sharing it).

const arr = new Array(3).fill({});


arr[0].prop = true;
assert.deepEqual(
arr, [
{prop: true},
{prop: true},
{prop: true},
]);

The next subsection explains how to fix this.

31.7.3 Do we need to create an Array filled with objects?

> new Array(3).fill(0)


[0, 0, 0]

For large sizes, the temporary Array can consume quite a bit of memory. The following
approach doesn’t have this downside but is less self-descriptive:

> Array.from({length: 3}, () => ({}))


[{}, {}, {}]

Instead of a temporary Array, we are using a temporary Array-like object.

31.7.4 Do we need to create a range of integers?

function createRange(start, end) {


return Array.from({length: end-start}, (_, i) => i+start);
}
assert.deepEqual(
createRange(2, 5),
[2, 3, 4]);

Here is an alternative, slightly hacky technique for creating integer ranges that start at
zero:

/** Returns an iterable */


function createRange(end) {
return new Array(end).keys();
}
assert.deepEqual(

https://exploringjs.com/impatient-js/ch_arrays.html 18/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

Array.from(createRange(4)),
[0, 1, 2, 3]);

This works because .keys() treats holes like undefined elements and lists their indices.

31.7.5 Use a Typed Array if the elements are all integers or all floats

When dealing with Arrays of integers or floats, we should consider Typed Arrays, which
were created for this purpose.

31.8 Multidimensional Arrays

JavaScript does not have real multidimensional Arrays; we need to resort to Arrays whose
elements are Arrays:

function initMultiArray(...dimensions) {
function initMultiArrayRec(dimIndex) {
if (dimIndex >= dimensions.length) {
return 0;
} else {
const dim = dimensions[dimIndex];
const arr = [];
for (let i=0; i<dim; i++) {
arr.push(initMultiArrayRec(dimIndex+1));
}
return arr;
}
}
return initMultiArrayRec(0);
}

const arr = initMultiArray(4, 3, 2);


arr[3][2][1] = 'X'; // last in each dimension
assert.deepEqual(arr, [
[ [ 0, 0 ], [ 0, 0 ], [ 0, 0 ] ],
[ [ 0, 0 ], [ 0, 0 ], [ 0, 0 ] ],
[ [ 0, 0 ], [ 0, 0 ], [ 0, 0 ] ],
[ [ 0, 0 ], [ 0, 0 ], [ 0, 'X' ] ],
]);

31.9 More Array features (advanced)

https://exploringjs.com/impatient-js/ch_arrays.html 19/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

In this section, we look at phenomena we don’t encounter often when working with
Arrays.

31.9.1 Array indices are (slightly special) property keys

You’d think that Array elements are special because we are accessing them via numbers.
But the square brackets operator [] for doing so is the same operator that is used for
accessing properties. It coerces any value (that is not a symbol) to a string. Therefore,
Array elements are (almost) normal properties (line A) and it doesn’t matter if we use
numbers or strings as indices (lines B and C):

const arr = ['a', 'b'];


arr.prop = 123;
assert.deepEqual(
Object.keys(arr),
['0', '1', 'prop']); // (A)

assert.equal(arr[0], 'a'); // (B)


assert.equal(arr['0'], 'a'); // (C)

To make matters even more confusing, this is only how the language specification defines
things (the theory of JavaScript, if you will). Most JavaScript engines optimize under the
hood and do use actual integers to access Array elements (the practice of JavaScript, if you
will).

Property keys (strings!) that are used for Array elements are called indices. A string str is
an index if converting it to a 32-bit unsigned integer and back results in the original value.
Written as a formula:

ToString(ToUint32(str)) === str

31.9.1.1 Listing indices

When listing property keys, indices are treated specially – they always come first and are
sorted like numbers ('2' comes before '10'):

const arr = [];


arr.prop = true;
arr[1] = 'b';
arr[0] = 'a';

assert.deepEqual(

https://exploringjs.com/impatient-js/ch_arrays.html 20/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

Object.keys(arr),
['0', '1', 'prop']);

Note that .length, .entries() and .keys() treat Array indices as numbers and ignore non-
index properties:

assert.equal(arr.length, 2);
assert.deepEqual(
Array.from(arr.keys()), [0, 1]);
assert.deepEqual(
Array.from(arr.entries()), [[0, 'a'], [1, 'b']]);

We used Array.from() to convert the iterables returned by .keys() and .entries() to Arrays.

31.9.2 Arrays are dictionaries and can have holes

We distinguish two kinds of Arrays in JavaScript:

An Array arr is dense if all indices i, with 0 ≤ i < arr.length, exist. That is, the indices
form a contiguous range.
An Array is sparse if the range of indices has holes in it. That is, some indices are
missing.

Arrays can be sparse in JavaScript because Arrays are actually dictionaries from indices to
values.

Recommendation: avoid holes

So far, we have only seen dense Arrays and it’s indeed recommended to avoid holes:
They make our code more complicated and are not handled consistently by Array
methods. Additionally, JavaScript engines optimize dense Arrays, making them faster.

31.9.2.1 Creating holes

We can create holes by skipping indices when assigning elements:

const arr = [];


arr[0] = 'a';
arr[2] = 'c';

assert.deepEqual(Object.keys(arr), ['0', '2']); // (A)

assert.equal(0 in arr, true); // element


assert.equal(1 in arr, false); // hole
https://exploringjs.com/impatient-js/ch_arrays.html 21/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

In line A, we are using Object.keys() because arr.keys() treats holes as if they were
undefined elements and does not reveal them.

Another way of creating holes is to skip elements in Array literals:

const arr = ['a', , 'c'];

assert.deepEqual(Object.keys(arr), ['0', '2']);

We can also delete Array elements:

const arr = ['a', 'b', 'c'];


assert.deepEqual(Object.keys(arr), ['0', '1', '2']);
delete arr[1];
assert.deepEqual(Object.keys(arr), ['0', '2']);

31.9.2.2 How do Array operations treat holes?

Alas, there are many different ways in which Array operations treat holes.

Some Array operations remove holes:

> ['a',,'b'].filter(x => true)


[ 'a', 'b' ]

Some Array operations ignore holes:

> ['a', ,'a'].every(x => x === 'a')


true

Some Array operations ignore but preserve holes:

> ['a',,'b'].map(x => 'c')


[ 'c', , 'c' ]

Some Array operations treat holes as undefined elements:

> Array.from(['a',,'b'], x => x)


[ 'a', undefined, 'b' ]
> Array.from(['a',,'b'].entries())
[[0, 'a'], [1, undefined], [2, 'b']]

Object.keys() works differently than .keys() (strings vs. numbers, holes don’t have keys):

> Array.from(['a',,'b'].keys())
[ 0, 1, 2 ]
https://exploringjs.com/impatient-js/ch_arrays.html 22/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

> Object.keys(['a',,'b'])
[ '0', '2' ]

There is no rule to remember here. If it ever matters how an Array operation treats holes,
the best approach is to do a quick test in a console.

31.10 Adding and removing elements (destructively and non-


destructively)

JavaScript’s Array is quite flexible and more like a combination of array, stack, and queue.
This section explores ways of adding and removing Array elements. Most operations can
be performed both destructively (modifying the Array) and non-destructively (producing a
modified copy).

31.10.1 Prepending elements and Arrays

In the following code, we destructively prepend single elements to arr1 and an Array to
arr2:

const arr1 = ['a', 'b'];


arr1.unshift('x', 'y'); // prepend single elements
assert.deepEqual(arr1, ['x', 'y', 'a', 'b']);

const arr2 = ['a', 'b'];


arr2.unshift(...['x', 'y']); // prepend Array
assert.deepEqual(arr2, ['x', 'y', 'a', 'b']);

Spreading lets us unshift an Array into arr2.

Non-destructive prepending is done via spread elements:

const arr1 = ['a', 'b'];


assert.deepEqual(
['x', 'y', ...arr1], // prepend single elements
['x', 'y', 'a', 'b']);
assert.deepEqual(arr1, ['a', 'b']); // unchanged!

const arr2 = ['a', 'b'];


assert.deepEqual(
[...['x', 'y'], ...arr2], // prepend Array
['x', 'y', 'a', 'b']);
assert.deepEqual(arr2, ['a', 'b']); // unchanged!

https://exploringjs.com/impatient-js/ch_arrays.html 23/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

31.10.2 Appending elements and Arrays

In the following code, we destructively append single elements to arr1 and an Array to
arr2:

const arr1 = ['a', 'b'];


arr1.push('x', 'y'); // append single elements
assert.deepEqual(arr1, ['a', 'b', 'x', 'y']);

const arr2 = ['a', 'b'];


arr2.push(...['x', 'y']); // (A) append Array
assert.deepEqual(arr2, ['a', 'b', 'x', 'y']);

Spreading (...) lets us push an Array into arr2 (line A).

Non-destructive appending is done via spread elements:

const arr1 = ['a', 'b'];


assert.deepEqual(
[...arr1, 'x', 'y'], // append single elements
['a', 'b', 'x', 'y']);
assert.deepEqual(arr1, ['a', 'b']); // unchanged!

const arr2 = ['a', 'b'];


assert.deepEqual(
[...arr2, ...['x', 'y']], // append Array
['a', 'b', 'x', 'y']);
assert.deepEqual(arr2, ['a', 'b']); // unchanged!

31.10.3 Removing elements

These are three destructive ways of removing Array elements:

// Destructively remove first element:


const arr1 = ['a', 'b', 'c'];
assert.equal(arr1.shift(), 'a');
assert.deepEqual(arr1, ['b', 'c']);

// Destructively remove last element:


const arr2 = ['a', 'b', 'c'];
assert.equal(arr2.pop(), 'c');
assert.deepEqual(arr2, ['a', 'b']);

// Remove one or more elements anywhere:


https://exploringjs.com/impatient-js/ch_arrays.html 24/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

const arr3 = ['a', 'b', 'c', 'd'];


assert.deepEqual(arr3.splice(1, 2), ['b', 'c']);
assert.deepEqual(arr3, ['a', 'd']);

.splice() is covered in more detail in the quick reference at the end of this chapter.

Destructuring via a rest element lets us non-destructively remove elements from the
beginning of an Array (destructuring is covered later).

const arr1 = ['a', 'b', 'c'];


// Ignore first element, extract remaining elements
const [, ...arr2] = arr1;

assert.deepEqual(arr2, ['b', 'c']);


assert.deepEqual(arr1, ['a', 'b', 'c']); // unchanged!

Alas, a rest element must come last in an Array. Therefore, we can only use it to extract
suffixes.

Exercise: Implementing a queue via an Array

exercises/arrays/queue_via_array_test.mjs

31.11 Methods: iteration and transformation (.find(), .map(), .filter(), etc.)

In this section, we take a look at Array methods for iterating over Arrays and for
transforming Arrays.

31.11.1 Callbacks for iteration and transformation methods

All iteration and transformation methods use callbacks. The former feed all iterated values
to their callbacks; the latter ask their callbacks how to transform Arrays.

These callbacks have type signatures that look as follows:

callback: (value: T, index: number, array: Array<T>) => boolean

That is, the callback gets three parameters (it is free to ignore any of them):

value is the most important one. This parameter holds the iterated value that is
currently being processed.
index can additionally tell the callback what the index of the iterated value is.

https://exploringjs.com/impatient-js/ch_arrays.html 25/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

arraypoints to the current Array (the receiver of the method call). Some algorithms
need to refer to the whole Array – e.g., to search it for answers. This parameter lets us
write reusable callbacks for such algorithms.

What the callback is expected to return depends on the method it is passed to. Possibilities
include:

.map() fills its result with the values returned by its callback:

> ['a', 'b', 'c'].map(x => x + x)


[ 'aa', 'bb', 'cc' ]

.find() returns the first Array element for which its callback returns true:

> ['a', 'bb', 'ccc'].find(str => str.length >= 2)


'bb'

Both of these methods are described in more detail later.

31.11.2 Searching elements: .find(), .findIndex()

.find() returns the first element for which its callback returns a truthy value (and
undefined if it can’t find anything):

> [6, -5, 8].find(x => x < 0)


-5
> [6, 5, 8].find(x => x < 0)
undefined

.findIndex() returns the index of the first element for which its callback returns a truthy
value (and -1 if it can’t find anything):

> [6, -5, 8].findIndex(x => x < 0)


1
> [6, 5, 8].findIndex(x => x < 0)
-1

.findIndex() can be implemented as follows:

function findIndex(arr, callback) {


for (const [i, x] of arr.entries()) {
if (callback(x, i, arr)) {
return i;
}
}
https://exploringjs.com/impatient-js/ch_arrays.html 26/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

return -1;
}

31.11.3 .map(): copy while giving elements new values

.map() returns a modified copy of the receiver. The elements of the copy are the results of
applying map’s callback to the elements of the receiver.

All of this is easier to understand via examples:

> [1, 2, 3].map(x => x * 3)


[ 3, 6, 9 ]
> ['how', 'are', 'you'].map(str => str.toUpperCase())
[ 'HOW', 'ARE', 'YOU' ]
> [true, true, true].map((_x, index) => index)
[ 0, 1, 2 ]

.map() can be implemented as follows:

function map(arr, mapFunc) {


const result = [];
for (const [i, x] of arr.entries()) {
result.push(mapFunc(x, i, arr));
}
return result;
}

Exercise: Numbering lines via .map()

exercises/arrays/number_lines_test.mjs

31.11.4 .flatMap(): mapping to zero or more values

The type signature of Array<T>.prototype.flatMap() is:

.flatMap<U>(
callback: (value: T, index: number, array: T[]) => U|Array<U>,
thisValue?: any
): U[]

Both .map() and .flatMap() take a function callback as a parameter that controls how an
input Array is translated to an output Array:

https://exploringjs.com/impatient-js/ch_arrays.html 27/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

With .map(), each input Array element is translated to exactly one output element.
That is, callback returns a single value.
With .flatMap(), each input Array element is translated to zero or more output
elements. That is, callback returns an Array of values (it can also return non-Array
values, but that is rare).

This is .flatMap() in action:

> ['a', 'b', 'c'].flatMap(x => [x,x])


[ 'a', 'a', 'b', 'b', 'c', 'c' ]
> ['a', 'b', 'c'].flatMap(x => [x])
[ 'a', 'b', 'c' ]
> ['a', 'b', 'c'].flatMap(x => [])
[]

We’ll consider use cases next, before exploring how this method could be implemented.

31.11.4.1 Use case: filtering and mapping at the same time

The result of the Array method .map() always has the same length as the Array it is
invoked on. That is, its callback can’t skip Array elements it isn’t interested in. The ability
of .flatMap() to do so is useful in the next example.

We will use the following function processArray() to create an Array that we’ll then filter
and map via .flatMap():

function processArray(arr, callback) {


return arr.map(x => {
try {
return { value: callback(x) };
} catch (e) {
return { error: e };
}
});
}

Next, we create an Array results via processArray():

const results = processArray([1, -5, 6], throwIfNegative);


assert.deepEqual(results, [
{ value: 1 },
{ error: new Error('Illegal value: -5') },
{ value: 6 },
]);
https://exploringjs.com/impatient-js/ch_arrays.html 28/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

function throwIfNegative(value) {
if (value < 0) {
throw new Error('Illegal value: '+value);
}
return value;
}

We can now use .flatMap() to extract just the values or just the errors from results:

const values = results.flatMap(


result => result.value ? [result.value] : []);
assert.deepEqual(values, [1, 6]);

const errors = results.flatMap(


result => result.error ? [result.error] : []);
assert.deepEqual(errors, [new Error('Illegal value: -5')]);

31.11.4.2 Use case: mapping single input values to multiple output values

The Array method .map() maps each input Array element to one output element. But what
if we want to map it to multiple output elements?

That becomes necessary in the following example:

> stringsToCodePoints(['many', 'a', 'moon'])


['m', 'a', 'n', 'y', 'a', 'm', 'o', 'o', 'n']

We want to convert an Array of strings to an Array of Unicode characters (code points).


The following function achieves that via .flatMap():

function stringsToCodePoints(strs) {
return strs.flatMap(str => Array.from(str));
}

31.11.4.3 A simple implementation

We can implement .flatMap() as follows. Note: This implementation is simpler than the
built-in version, which, for example, performs more checks.

function flatMap(arr, mapFunc) {


const result = [];
for (const [index, elem] of arr.entries()) {
const x = mapFunc(elem, index, arr);

https://exploringjs.com/impatient-js/ch_arrays.html 29/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

// We allow mapFunc() to return non-Arrays


if (Array.isArray(x)) {
result.push(...x);
} else {
result.push(x);
}
}
return result;
}

Exercises: .flatMap()

exercises/arrays/convert_to_numbers_test.mjs

exercises/arrays/replace_objects_test.mjs

31.11.5 .filter(): only keep some of the elements

The Array method .filter() returns an Array collecting all elements for which the
callback returns a truthy value.

For example:

> [-1, 2, 5, -7, 6].filter(x => x >= 0)


[ 2, 5, 6 ]
> ['a', 'b', 'c', 'd'].filter((_x,i) => (i%2)===0)
[ 'a', 'c' ]

.filter() can be implemented as follows:

function filter(arr, filterFunc) {


const result = [];
for (const [i, x] of arr.entries()) {
if (filterFunc(x, i, arr)) {
result.push(x);
}
}
return result;
}

Exercise: Removing empty lines via .filter()

exercises/arrays/remove_empty_lines_filter_test.mjs

https://exploringjs.com/impatient-js/ch_arrays.html 30/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

31.11.6 .reduce(): deriving a value from an Array (advanced)

Method .reduce() is a powerful tool for computing a “summary” of an Array arr. A


summary can be any kind of value:

A number. For example, the sum of all elements of arr.


An Array. For example, a copy of arr, where each element is twice the original
element.
Etc.

reduce is also known as foldl (“fold left”) in functional programming and popular there.
One caveat is that it can make code difficult to understand.

.reduce() has the following type signature (inside an Array<T>):

.reduce<U>(
callback: (accumulator: U, element: T, index: number, array: T[]) => U,
init?: U)
: U

T is the type of the Array elements, U is the type of the summary. The two may or may not
be different. accumulator is just another name for “summary”.

To compute the summary of an Array arr, .reduce() feeds all Array elements to its
callback one at a time:

const accumulator_0 = callback(init, arr[0]);


const accumulator_1 = callback(accumulator_0, arr[1]);
const accumulator_2 = callback(accumulator_1, arr[2]);
// Etc.

callback combines the previously computed summary (stored in its parameter accumulator)
with the current Array element and returns the next accumulator. The result of .reduce() is
the final accumulator – the last result of callback after it has visited all elements.

In other words: callback does most of the work; .reduce() just invokes it in a useful
manner.

We could say that the callback folds Array elements into the accumulator. That’s why this
operation is called “fold” in functional programming.

31.11.6.1 A first example

https://exploringjs.com/impatient-js/ch_arrays.html 31/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

Let’s look at an example of .reduce() in action: function addAll() computes the sum of all
numbers in an Array arr.

function addAll(arr) {
const startSum = 0;
const callback = (sum, element) => sum + element;
return arr.reduce(callback, startSum);
}
assert.equal(addAll([1, 2, 3]), 6); // (A)
assert.equal(addAll([7, -4, 2]), 5);

In this case, the accumulator holds the sum of all Array elements that callback has already
visited.

How was the result 6 derived from the Array in line A? Via the following invocations of
callback:

callback(0, 1) --> 1
callback(1, 2) --> 3
callback(3, 3) --> 6

Notes:

The first parameters are the current accumulators (starting with parameter init of
.reduce()).
The second parameters are the current Array elements.
The results are the next accumulators.
The last result of callback is also the result of .reduce().

Alternatively, we could have implemented addAll() via a for-of loop:

function addAll(arr) {
let sum = 0;
for (const element of arr) {
sum = sum + element;
}
return sum;
}

It’s hard to say which of the two implementations is “better”: the one based on .reduce() is
a little more concise, while the one based on for-of may be a little easier to understand –
especially if someone is not familiar with functional programming.

31.11.6.2 Example: finding indices via .reduce()

https://exploringjs.com/impatient-js/ch_arrays.html 32/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

The following function is an implementation of the Array method .indexOf(). It returns


the first index at which the given searchValue appears inside the Array arr:

const NOT_FOUND = -1;


function indexOf(arr, searchValue) {
return arr.reduce(
(result, elem, index) => {
if (result !== NOT_FOUND) {
// We have already found something: don’t change anything
return result;
} else if (elem === searchValue) {
return index;
} else {
return NOT_FOUND;
}
},
NOT_FOUND);
}
assert.equal(indexOf(['a', 'b', 'c'], 'b'), 1);
assert.equal(indexOf(['a', 'b', 'c'], 'x'), -1);

One limitation of .reduce() is that we can’t finish early (in a for-of loop, we can break).
Here, we always immediately return the result once we have found it.

31.11.6.3 Example: doubling Array elements

Function double(arr) returns a copy of inArr whose elements are all multiplied by 2:

function double(inArr) {
return inArr.reduce(
(outArr, element) => {
outArr.push(element * 2);
return outArr;
},
[]);
}
assert.deepEqual(
double([1, 2, 3]),
[2, 4, 6]);

We modify the initial value [] by pushing into it. A non-destructive, more functional
version of double() looks as follows:

https://exploringjs.com/impatient-js/ch_arrays.html 33/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

function double(inArr) {
return inArr.reduce(
// Don’t change `outArr`, return a fresh Array
(outArr, element) => [...outArr, element * 2],
[]);
}
assert.deepEqual(
double([1, 2, 3]),
[2, 4, 6]);

This version is more elegant but also slower and uses more memory.

Exercises: .reduce()

map() via .reduce(): exercises/arrays/map_via_reduce_test.mjs


filter() via .reduce(): exercises/arrays/filter_via_reduce_test.mjs
countMatches() via .reduce(): exercises/arrays/count_matches_via_reduce_test.mjs

31.12 .sort(): sorting Arrays

.sort() has the following type definition:

sort(compareFunc?: (a: T, b: T) => number): this

By default, .sort() sorts string representations of the elements. These representations are
compared via <. This operator compares lexicographically (the first characters are most
significant). We can see that when sorting numbers:

> [200, 3, 10].sort()


[ 10, 200, 3 ]

When sorting human-language strings, we need to be aware that they are compared
according to their code unit values (char codes):

> ['pie', 'cookie', 'éclair', 'Pie', 'Cookie', 'Éclair'].sort()


[ 'Cookie', 'Pie', 'cookie', 'pie', 'Éclair', 'éclair' ]

All unaccented uppercase letters come before all unaccented lowercase letters, which
come before all accented letters. We can use Intl, the JavaScript internationalization API if
we want proper sorting for human languages.

.sort() sorts in place; it changes and returns its receiver:

https://exploringjs.com/impatient-js/ch_arrays.html 34/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

> const arr = ['a', 'c', 'b'];


> arr.sort() === arr
true
> arr
[ 'a', 'b', 'c' ]

31.12.1 Customizing the sort order

We can customize the sort order via the parameter compareFunc, which must return a
number that is:

negative if a < b
zero if a === b
positive if a > b

Tip for remembering these rules

A negative number is less than zero (etc.).

31.12.2 Sorting numbers

We can use this helper function to sort numbers:

function compareNumbers(a, b) {
if (a < b) {
return -1;
} else if (a === b) {
return 0;
} else {
return 1;
}
}
assert.deepEqual(
[200, 3, 10].sort(compareNumbers),
[3, 10, 200]);

The following is a quick and dirty alternative.

> [200, 3, 10].sort((a,b) => a - b)


[ 3, 10, 200 ]

The downsides of this approach are:

It is cryptic.
https://exploringjs.com/impatient-js/ch_arrays.html 35/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

There is a risk of numeric overflow or underflow, if a-b becomes a large positive or


negative number.

31.12.3 Sorting objects

We also need to use a compare function if we want to sort objects. As an example, the
following code shows how to sort objects by age.

const arr = [ {age: 200}, {age: 3}, {age: 10} ];


assert.deepEqual(
arr.sort((obj1, obj2) => obj1.age - obj2.age),
[{ age: 3 }, { age: 10 }, { age: 200 }] );

Exercise: Sorting objects by name

exercises/arrays/sort_objects_test.mjs

31.13 Quick reference: Array

Legend:

R: method does not change the Array (non-destructive).


W: method changes the Array (destructive).

31.13.1 new Array()

new Array(n) creates an Array of length n that contains n holes:

// Trailing commas are always ignored.


// Therefore: number of commas = number of holes
assert.deepEqual(new Array(3), [,,,]);

new Array() creates an empty Array. However, I recommend to always use [] instead.

31.13.2 Static methods of Array

Array.from<T>(iterable: Iterable<T> | ArrayLike<T>): T[] [ES6]

Array.from<T,U>(iterable: Iterable<T> | ArrayLike<T>, mapFunc: (v: T, k: number) =>

U, thisArg?: any): U[] [ES6]

Converts an iterable or an Array-like object to an Array. Optionally, the input values


can be translated via mapFunc before they are added to the output Array.

https://exploringjs.com/impatient-js/ch_arrays.html 36/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

Examples:

> Array.from(new Set(['a', 'b'])) // iterable


[ 'a', 'b' ]
> Array.from({length: 2, 0:'a', 1:'b'}) // Array-like object
[ 'a', 'b' ]

Array.of<T>(...items: T[]): T[] [ES6]

This static method is mainly useful for subclasses of Array, where it serves as a
custom Array literal:

class MyArray extends Array {}

assert.equal(
MyArray.of('a', 'b') instanceof MyArray, true);

31.13.3 Methods of Array.prototype

.at(index: number): T | undefined [R, ES2022]

Returns the Array element at index. If index is negative, it is added to .length before it
is used (-1 becomes this.length-1, etc.).

> ['a', 'b', 'c'].at(0)


'a'
> ['a', 'b', 'c'].at(-1)
'c'

.concat(...items: Array<T[] | T>): T[] [R, ES3]

Returns a new Array that is the concatenation of the receiver and all items. Non-
Array parameters (such as 'b' in the following example) are treated as if they were
Arrays with single elements.

> ['a'].concat('b', ['c', 'd'])


[ 'a', 'b', 'c', 'd' ]

.copyWithin(target: number, start: number, end=this.length): this [W, ES6]

Copies the elements whose indices range from (including) start to (excluding) end to
indices starting with target. Overlapping is handled correctly.

https://exploringjs.com/impatient-js/ch_arrays.html 37/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

> ['a', 'b', 'c', 'd'].copyWithin(0, 2, 4)


[ 'c', 'd', 'c', 'd' ]

If start or end is negative, then .length is added to it.

.entries(): Iterable<[number, T]> [R, ES6]

Returns an iterable over [index, element] pairs.

> Array.from(['a', 'b'].entries())


[ [ 0, 'a' ], [ 1, 'b' ] ]

.every(callback: (value: T, index: number, array: Array<T>) => boolean, thisArg?:

any): boolean [R, ES5]

Returns true if callback returns a truthy value for every element. Otherwise, it
returns false. It stops as soon as it receives a falsy value. This method corresponds to
universal quantification (“for all”, ∀) in mathematics.

> [1, 2, 3].every(x => x > 0)


true
> [1, -2, 3].every(x => x > 0)
false

Related method: .some() (“exists”).

.fill(value: T, start=0, end=this.length): this [W, ES6]

Assigns value to every index between (including) start and (excluding) end.

> [0, 1, 2].fill('a')


[ 'a', 'a', 'a' ]

Caveat: Don’t use this method to fill an Array with an object obj; then each element
will refer to obj (sharing it). In this case, it’s better to use Array.from().

.filter(callback: (value: T, index: number, array: Array<T>) => any, thisArg?: any):

T[] [R, ES5]

Returns an Array with only those elements for which callback returns a truthy value.

> [1, -2, 3].filter(x => x > 0)


[ 1, 3 ]

https://exploringjs.com/impatient-js/ch_arrays.html 38/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

.find(predicate: (value: T, index: number, obj: T[]) => boolean, thisArg?: any): T |

undefined [R, ES6]

The result is the first element for which predicate returns a truthy value. If there is no
such element, the result is undefined.

> [1, -2, 3].find(x => x < 0)


-2
> [1, 2, 3].find(x => x < 0)
undefined

.findIndex(predicate: (value: T, index: number, obj: T[]) => boolean, thisArg?:

any): number [R, ES6]

The result is the index of the first element for which predicate returns a truthy value.
If there is no such element, the result is -1.

> [1, -2, 3].findIndex(x => x < 0)


1
> [1, 2, 3].findIndex(x => x < 0)
-1

.flat(depth = 1): any[] [R, ES2019]

“Flattens” an Array: It descends into the Arrays that are nested inside the input
Array and creates a copy where all values it finds at level depth or lower are moved to
the top level.

> [ 1,2, [3,4], [[5,6]] ].flat(0) // no change


[ 1, 2, [3,4], [[5,6]] ]

> [ 1,2, [3,4], [[5,6]] ].flat(1)


[1, 2, 3, 4, [5,6]]

> [ 1,2, [3,4], [[5,6]] ].flat(2)


[1, 2, 3, 4, 5, 6]

.flatMap<U>(callback: (value: T, index: number, array: T[]) => U|Array<U>,

thisValue?: any): U[] [R, ES2019]

The result is produced by invoking callback() for each element of the original Array
and concatenating the Arrays it returns.

https://exploringjs.com/impatient-js/ch_arrays.html 39/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

> ['a', 'b', 'c'].flatMap(x => [x,x])


[ 'a', 'a', 'b', 'b', 'c', 'c' ]
> ['a', 'b', 'c'].flatMap(x => [x])
[ 'a', 'b', 'c' ]
> ['a', 'b', 'c'].flatMap(x => [])
[]

.forEach(callback: (value: T, index: number, array: Array<T>) => void, thisArg?:

any): void [R, ES5]

Calls callback for each element.

['a', 'b'].forEach((x, i) => console.log(x, i))

// Output:
// 'a', 0
// 'b', 1

A for-of loop is usually a better choice: it’s faster, supports break and can iterate over
arbitrary iterables.

.includes(searchElement: T, fromIndex=0): boolean [R, ES2016]

Returns true if the receiver has an element whose value is searchElement and false,
otherwise. Searching starts at index fromIndex.

> [0, 1, 2].includes(1)


true
> [0, 1, 2].includes(5)
false

.indexOf(searchElement: T, fromIndex=0): number [R, ES5]

Returns the index of the first element that is strictly equal to searchElement. Returns -1
if there is no such element. Starts searching at index fromIndex, visiting higher indices
next.

> ['a', 'b', 'a'].indexOf('a')


0
> ['a', 'b', 'a'].indexOf('a', 1)
2
> ['a', 'b', 'a'].indexOf('c')
-1

https://exploringjs.com/impatient-js/ch_arrays.html 40/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

.join(separator = ','): string [R, ES1]

Creates a string by concatenating string representations of all elements, separating


them with separator.

> ['a', 'b', 'c'].join('##')


'a##b##c'
> ['a', 'b', 'c'].join()
'a,b,c'

.keys(): Iterable<number> [R, ES6]

Returns an iterable over the keys of the receiver.

> Array.from(['a', 'b'].keys())


[ 0, 1 ]

.lastIndexOf(searchElement: T, fromIndex=this.length-1): number [R, ES5]

Returns the index of the last element that is strictly equal to searchElement. Returns -1
if there is no such element. Starts searching at index fromIndex, visiting lower indices
next.

> ['a', 'b', 'a'].lastIndexOf('a')


2
> ['a', 'b', 'a'].lastIndexOf('a', 1)
0
> ['a', 'b', 'a'].lastIndexOf('c')
-1

.map<U>(mapFunc: (value: T, index: number, array: Array<T>) => U, thisArg?: any):

U[] [R, ES5]

Returns a new Array, in which every element is the result of mapFunc being applied to
the corresponding element of the receiver.

> [1, 2, 3].map(x => x * 2)


[ 2, 4, 6 ]
> ['a', 'b', 'c'].map((x, i) => i)
[ 0, 1, 2 ]

.pop(): T | undefined [W, ES3]

https://exploringjs.com/impatient-js/ch_arrays.html 41/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

Removes and returns the last element of the receiver. That is, it treats the end of the
receiver as a stack. The opposite of .push().

> const arr = ['a', 'b', 'c'];


> arr.pop()
'c'
> arr
[ 'a', 'b' ]

.push(...items: T[]): number [W, ES3]

Adds zero or more items to the end of the receiver. That is, it treats the end of the
receiver as a stack. The return value is the length of the receiver after the change. The
opposite of .pop().

> const arr = ['a', 'b'];


> arr.push('c', 'd')
4
> arr
[ 'a', 'b', 'c', 'd' ]

We can push an Array by spreading (...) it into arguments:

> const arr = ['x'];


> arr.push(...['y', 'z'])
3
> arr
[ 'x', 'y', 'z' ]

.reduce<U>(callback: (accumulator: U, element: T, index: number, array: T[]) => U,

init?: U): U [R, ES5]

This method produces a summary of the receiver: it feeds all Array elements to
callback,which combines a current summary (in parameter accumulator) with the
current Array element and returns the next accumulator:

const accumulator_0 = callback(init, arr[0]);


const accumulator_1 = callback(accumulator_0, arr[1]);
const accumulator_2 = callback(accumulator_1, arr[2]);
// Etc.

The result of .reduce() is the last result of callback after it has visited all Array
elements.

https://exploringjs.com/impatient-js/ch_arrays.html 42/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

> [1, 2, 3].reduce((accu, x) => accu + x, 0)


6
> [1, 2, 3].reduce((accu, x) => accu + String(x), '')
'123'

If no init is provided, the Array element at index 0 is used and the element at index 1
is visited first. Therefore, the Array must have at least length 1.

.reduceRight<U>(callback: (accumulator: U, element: T, index: number, array: T[]) =>

U, init?: U): U [R, ES5]

Works like .reduce(), but visits the Array elements backward, starting with the last
element.

> [1, 2, 3].reduceRight((accu, x) => accu + String(x), '')


'321'

.reverse(): this [W, ES1]

Rearranges the elements of the receiver so that they are in reverse order and then
returns the receiver.

> const arr = ['a', 'b', 'c'];


> arr.reverse()
[ 'c', 'b', 'a' ]
> arr
[ 'c', 'b', 'a' ]

.shift(): T | undefined [W, ES3]

Removes and returns the first element of the receiver. The opposite of .unshift().

> const arr = ['a', 'b', 'c'];


> arr.shift()
'a'
> arr
[ 'b', 'c' ]

.slice(start=0, end=this.length): T[] [R, ES3]

Returns a new Array containing the elements of the receiver whose indices are
between (including) start and (excluding) end.

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


[ 'b', 'c' ]
https://exploringjs.com/impatient-js/ch_arrays.html 43/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

> ['a', 'b'].slice() // shallow copy


[ 'a', 'b' ]

Negative indices are allowed and added to .length:

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


[ 'b', 'c' ]

.some(callback: (value: T, index: number, array: Array<T>) => boolean, thisArg?:

any): boolean [R, ES5]

Returns true if callback returns a truthy value for at least one element. Otherwise, it
returns false. It stops as soon as it receives a truthy value. This method corresponds
to existential quantification (“exists”, ∃) in mathematics.

> [1, 2, 3].some(x => x < 0)


false
> [1, -2, 3].some(x => x < 0)
true

Related method: .every() (“for all”).

.sort(compareFunc?: (a: T, b: T) => number): this [W, ES1]

Sorts the receiver and returns it. By default, it sorts string representations of the
elements. It does so lexicographically and according to the code unit values (char
codes) of the characters:

> ['pie', 'cookie', 'éclair', 'Pie', 'Cookie', 'Éclair'].sort()


[ 'Cookie', 'Pie', 'cookie', 'pie', 'Éclair', 'éclair' ]
> [200, 3, 10].sort()
[ 10, 200, 3 ]

We can customize the sort order via compareFunc, which returns a number that is:

negative if a < b
zero if a === b
positive if a > b

Trick for sorting numbers (with a risk of numeric overflow or underflow):

> [200, 3, 10].sort((a, b) => a - b)


[ 3, 10, 200 ]

.sort() is stable
https://exploringjs.com/impatient-js/ch_arrays.html 44/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

Since ECMAScript 2019, sorting is guaranteed to be stable: if elements are


considered equal by sorting, then sorting does not change the order of those
elements (relative to each other).

.splice(start: number, deleteCount=this.length-start, ...items: T[]): T[] [W, ES3]

At index start, it removes deleteCount elements and inserts the items. It returns the
deleted elements.

> const arr = ['a', 'b', 'c', 'd'];


> arr.splice(1, 2, 'x', 'y')
[ 'b', 'c' ]
> arr
[ 'a', 'x', 'y', 'd' ]

start can be negative and is added to .length if it is:

> ['a', 'b', 'c'].splice(-2, 2)


[ 'b', 'c' ]

.toString(): string [R, ES1]

Converts all elements to strings via String(), concatenates them while separating
them with commas, and returns the result.

> [1, 2, 3].toString()


'1,2,3'
> ['1', '2', '3'].toString()
'1,2,3'
> [].toString()
''

.unshift(...items: T[]): number [W, ES3]

Inserts the items at the beginning of the receiver and returns its length after this
modification.

> const arr = ['c', 'd'];


> arr.unshift('e', 'f')
4
> arr
[ 'e', 'f', 'c', 'd' ]

.values(): Iterable<T> [R, ES6]

https://exploringjs.com/impatient-js/ch_arrays.html 45/46
5/9/24, 5:53 PM Arrays (`Array`) • JavaScript for impatient programmers (ES2022 edition)

Returns an iterable over the values of the receiver.

> Array.from(['a', 'b'].values())


[ 'a', 'b' ]

31.13.4 Sources

TypeScript’s built-in typings


MDN web docs for JavaScript
ECMAScript language specification

Quiz

See quiz app.

Comments (8) Next: 32 Typed Arrays: handling binary data (advanced)

https://exploringjs.com/impatient-js/ch_arrays.html 46/46

You might also like