Array
).length
of an Arrayfor-of
and Arrays
.find()
, .map()
, .filter()
, etc.)
.find()
, .findIndex()
.map()
: copy while giving elements new values.flatMap()
: mapping to zero or more values.filter()
: only keep some of the elements.reduce()
: deriving a value from an Array (advanced).sort()
: sorting Arrays
Array<T>
new Array()
Array
Array<T>.prototype
Arrays play two roles in JavaScript:
In practice, these two roles are often mixed.
Notably, Arrays-as-sequences are so flexible that you can use them as (traditional) arrays, stacks and queues (see exercise later in this chapter).
The best way to create an Array, is via an Array literal:
The Array literal starts and ends with square brackets []
. It creates an Array with three elements: 'a'
, 'b'
and 'c'
.
To read an Array element, you put an index in square brackets (indices start at zero):
To change an Array element, you assign to an Array with an index:
The range of Array indices is 32 bits (excluding the maximum length): [0, 232−1)
.length
of an ArrayEvery 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:
If you write to the Array at the index of the length, you append an element:
Another way of (destructively) appending an element is via the Array method .push()
:
If you set .length
, you are pruning the Array, by removing elements:
To clear (empty) an Array, you can either set its .length
to zero:
Or you can assign a new empty Array to the variable storing the Array:
The latter approach has the advantage of not affecting other locations that point to the same Array. If, however, you do want to reset a shared Array for everyone, then you need the former approach.
Exercise: Removing empty lines via
.push()
exercises/arrays/remove_empty_lines_push_test.mjs
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:
That means that we can use spreading to create a copy of an Array:
Warning: Similar to spreading into object literals, spreading into Array literals creates shallow copies. That is, nested Arrays are not copied.
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']);
Method .keys()
lists the indices of an Array:
.keys()
returns an iterable. In line A, we spread to obtain 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):
Method .entries()
lists the contents of an Array as [index, element] pairs:
These are two ways of checking if a value is an Array:
instanceof
is usually fine. You 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 you can move data. For example, same-origin iframes in browsers. x instanceof Array
checks the prototype chain of x
and therefore returns false
if x
is an Array from another realm.
typeof
categorizes Arrays as objects:
for-of
and ArraysWe have already encountered the for-of
loop. This section briefly recaps how to use it for Arrays.
for-of
: iterating over elementsThe following for-of
loop iterates over the elements of an Array.
for-of
: iterating over [index, element] pairsThe 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);
}
// Output:
// 0, 'a'
// 1, 'b'
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 the brackets coerce all values (except symbols) to strings. Therefore, [0]
retrieves the value of the property whose key is '0'
.Array.from()
accepts Array-like objects and converts them to Arrays:
// If you omit .length, it is interpreted as 0
assert.deepEqual(
Array.from({}),
[]);
assert.deepEqual(
Array.from({length:2, 0:'a', 1:'b'}),
[ 'a', 'b' ]);
Array-like objects are relatively rare in modern JavaScript
Array-like objects used to be common before ES6; now you don’t see them very often.
There are two common ways of converting iterable and Array-like values to Arrays: spreading and Array.from()
.
...
)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),
['http://2ality.com', 'http://exploringjs.com']);
The conversion works, because the DOM collection is iterable.
Array.from()
(advanced)Array.from()
can be used in two modes.
Array.from()
: convertingThe first mode has the following type signature:
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()
: converting and mappingThe second mode of Array.from()
involves two parameters:
.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:
iterable
.mapFunc
with each iterated value. The optional parameter thisArg
specifies a this
for mapFunc
.mapFunc
to each iterated value.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:
The best way of creating an Array is via an Array literal. However, you can’t always use one: The Array may be too large, you may not know its length during development, or you may want to keep its length flexible. Then I recommend the following techniques for creating, and possibly filling, Arrays.
Note that the result has three holes (empty slots) – the last comma in an Array literal is always ignored.
Caveat: If you 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.
function createRange(start, end) {
return Array.from({length: end-start}, (_, i) => i+start);
}
assert.deepEqual(
createRange(2, 5),
[2, 3, 4]);
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(
[...createRange(4)],
[0, 1, 2, 3]);
This works, because .keys()
treats holes like undefined
elements and lists their indices.
If you are dealing with Arrays of integers or floats, consider Typed Arrays – which were created for this purpose.
JavaScript does not have real multidimensional Arrays; you 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' ] ],
]);
In this section, we look at phenomena, you don’t encounter often when working with Arrays.
You’d think that Array elements are special, because you 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 you 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
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(
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(
[...arr.keys()], [0, 1]);
assert.deepEqual(
[...arr.entries()], [[0, 'a'], [1, 'b']]);
We used a spread element (...
) to convert the iterables returned by .keys()
and .entries()
to Arrays.
We distinguish two kinds of Arrays in JavaScript:
arr
is dense if all indices i
, with 0 ≤ i
< arr.length
, exist. That is, the indices form a contiguous range.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 your code more complicated and are not handled consistently by Array methods. Additionally, JavaScript engines optimize dense Arrays, making them faster.
You 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
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:
You 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']);
Alas, there are many different ways in which Array operations treat holes.
Some Array operations remove holes:
Some Array operations ignore holes:
Some Array operations ignore, but preserve holes:
Some Array operations treat holes as undefined
elements:
> Array.from(['a',,'b'], x => x)
[ 'a', undefined, 'b' ]
> [...['a',,'b'].entries()]
[[0, 'a'], [1, undefined], [2, 'b']]
Object.keys()
works differently than .keys()
(strings vs. numbers, holes don’t have keys):
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.
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).
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!
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']); // append Array
assert.deepEqual(arr2, ['a', 'b', 'x', 'y']);
Spreading lets us push an Array into arr2
.
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!
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:
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 you 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, you can only use it to extract suffixes.
Exercise: Implementing a queue via an Array
exercises/arrays/queue_via_array_test.mjs
.find()
, .map()
, .filter()
, etc.)In this section, we take a look at Array methods for iterating over Arrays and for transforming Arrays.
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.
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.array
points 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 you write reusable callbacks for such algorithms.What the callback is expected to return, depends on the method it is passed to. Possibilities include:
.find()
.map()
.find()
, .findIndex()
.find()
returns the first element for which its callback returns a truthy value (and undefined
if it can’t find anything):
.findIndex()
returns the index of the first element for which its callback returns a truthy value (and -1
if it can’t find anything):
.findIndex()
can be implemented as follows:
function findIndex(arr, callback) {
for (const [i, x] of arr.entries()) {
if (callback(x, i, arr)) {
return i;
}
}
return -1;
}
.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
.flatMap()
: mapping to zero or more valuesThe type signature of Array<T>.prototype.flatMap()
is:
Both .map()
and .flatMap()
take a function f
as a parameter that controls how an input Array is translated to an output Array:
.map()
, each input Array element is translated to exactly one output element. That is, f
returns a single value..flatMap()
, each input Array element is translated to zero or more output elements. That is, f
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 => [])
[]
You could implement .flatMap()
as follows. Note: this implementation is simpler than the built-in version, which, e.g., performs more checks.
function flatMap(arr, mapFunc) {
const result = [];
for (const [index, elem] of arr.entries()) {
const x = mapFunc(elem, index, arr);
// We allow mapFunc() to return non-Arrays
if (Array.isArray(x)) {
result.push(...x);
} else {
result.push(x);
}
}
return result;
}
What is .flatMap()
good for? Let’s look at use cases!
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: processArray()
returns an Array where each element is either a wrapped value or a wrapped error.
function processArray(arr, process) {
return arr.map(x => {
try {
return { value: process(x) };
} catch (e) {
return { error: e };
}
});
}
The following code shows processArray()
in action:
let err;
function throwIfNegative(value) {
if (value < 0) {
throw (err = new Error('Illegal value: '+value));
}
return value;
}
const results = processArray([1, -5, 6], throwIfNegative);
assert.deepEqual(results, [
{ value: 1 },
{ error: err },
{ value: 6 },
]);
.flatMap()
enables us 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, [err]);
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:
We want to convert an Array of strings to an Array of Unicode characters (code points). The following function achieves that, via .flatMap()
:
Exercises:
.flatMap()
exercises/arrays/convert_to_numbers_test.mjs
exercises/arrays/replace_objects_test.mjs
.filter()
: only keep some of the elementsThe 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
.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:
arr
.arr
, where each element is twice the original element.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>
):
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.
You could say that the callback folds Array elements into the accumulator. That’s why this operation is called “fold” in functional programming.
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:
init
of .reduce()
).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 you are not familiar with functional programming.
.reduce()
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 you can’t finish early (in a for-of
loop, you can break
). Here, we always immediately return the result, once we have found it.
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:
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
.sort()
: sorting Arrays.sort()
has the following type definition:
By default, .sort()
sorts string representations of the elements. These representations are compared via <
. This operator compares lexicographically (the first characters are most significant). You can see that when sorting numbers:
When sorting human-language strings, you 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' ]
As you can see, all unaccented uppercase letters come before all unaccented lowercase letter, which come before all accented letters. Use Intl
, the JavaScript internationalization API, if you want proper sorting for human languages.
Note that .sort()
sorts in place: it changes and returns its receiver:
You can customize the sort order via the parameter compareFunc
, which must return a number that is:
a < b
a === b
a > b
Tip for remembering these rules
A negative number is less than zero (etc.).
You 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.
The downsides of this approach, are:
a-b
becomes a large positive or negative number.You also need to use a compare function if you 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
Array<T>
Legend:
R
: method does not change the Array (non-destructive).W
: method changes the Array (destructive).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.
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.
An Array-like object has a .length
and properties whose keys are indices (roughly, string representations of non-negative integers):
Examples:
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:
Array<T>.prototype
.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.
.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.
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.
.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.
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
.
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.
.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
.
.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
.
.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.
.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.
.forEach(callback: (value: T, index: number, array: Array<T>) => void, thisArg?: any): void
[R, ES5]
Calls callback
for each element.
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
.
.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.
.join(separator = ','): string
[R, ES1]
Creates a string by concatenating string representations of all elements, separating them with separator
.
.keys(): Iterable<number>
[R, ES6]
Returns an iterable over the keys of the receiver.
.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.
.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.
.pop(): T | undefined
[W, ES3]
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()
.
.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()
.
.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.
> [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.
.reverse(): this
[W, ES1]
Rearranges the elements of the receiver so that they are in reverse order and then returns the receiver.
.shift(): T | undefined
[W, ES3]
Removes and returns the first element of the receiver. The opposite of .unshift()
.
.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
.
Negative indices are allowed and added to .length
:
.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.
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 ]
You can customize the sort order via compareFunc
, which returns a number that is:
a < b
a === b
a > b
Trick for sorting numbers (with a risk of numeric overflow or underflow):
.sort()
is stable
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:
.toString(): string
[R, ES1]
Converts all elements to strings via String()
, concatenates them, while separating them with commas, and returns the result.
.unshift(...items: T[]): number
[W, ES3]
Inserts the items
at the beginning of the receiver and returns its length after this modification.
.values(): Iterable<T>
[R, ES6]
Returns an iterable over the values of the receiver.
Quiz
See quiz app.