Arrays and Array Processing - Learning JavaScript (2016)

Learning JavaScript (2016)

Chapter 8. Arrays and Array Processing

JavaScript’s array methods are among my favorite features of the language. So many programming problems involve the manipulation of collections of data, and fluency with JavaScript’s array methods will make that easy. Getting comfortable with these methods is also a great way to attain the next level of JavaScript mastery.

A Review of Arrays

Before we dive in, let’s remind ourselves of the basics of arrays. Arrays (unlike objects) are inherently ordered, with zero-based numeric indices. Arrays in JavaScript can be nonhomogeneous, meaning the elements in an array do not need to be the same type (it follows from this that arrays can have other arrays as elements, or objects). Literal arrays are constructed with square brackets, and the same square brackets are used to access elements by index. Every array has a length property, which tells you how many elements are in the array. Assigning to an index that’s larger than the array will automatically make the array larger, with unused indexes getting the value undefined. You can also use the Array constructor to create arrays, though this is seldom necessary. Make sure all of the following makes sense to you before you proceed:

// array literals

const arr1 = [1, 2, 3]; // array of numbers

const arr2 = ["one", 2, "three"]; // nonhomogeneous array

const arr3 = [[1, 2, 3], ["one", 2, "three"]]; // array containing arrays

const arr4 = [ // nonhomogeneous array

{ name: "Fred", type: "object", luckyNumbers = [5, 7, 13] },

[

{ name: "Susan", type: "object" },

{ name: "Anthony", type: "object" },

],

1,

function() { return "arrays can contain functions too"; },

"three",

];

// accessing elements

arr1[0]; // 1

arr1[2]; // 3

arr3[1]; // ["one", 2, "three"]

arr4[1][0]; // { name: "Susan", type: "object" }

// array length

arr1.length; // 3

arr4.length; // 5

arr4[1].length; // 2

// increasing array size

arr1[4] = 5;

arr1; // [1, 2, 3, undefined, 5]

arr1.length; // 5

// accessing (not assigning) an index larger than the array

// does *not* change the size of the array

arr2[10]; // undefined

arr2.length; // 3

// Array constructor (rarely used)

const arr5 = new Array(); // empty array

const arr6 = new Array(1, 2, 3); // [1, 2, 3]

const arr7 = new Array(2); // array of length 2 (all elements undefined)

const arr8 = new Array("2"); // ["2"]

Array Content Manipulation

Before we get into the exciting methods, let’s first consider the (very useful) methods for manipulating arrays. One aspect of array methods that is unfortunately confusing is the difference between methods that modify the array “in place” and those that return a new array. There’s no convention, and this is just one of these things you have to memorize (for example, that push modifies an array in place but concat returns a new array).

NOTE

Some languages, like Ruby, have conventions that make it easy to tell if a method modifies something in place or returns a copy. For example, in Ruby, if you have a string str, and you call str.downcase, it will return a lowercased version, but str will remain unchanged. On the other hand, if you call str.downcase!, it will modify str in place. The fact that the standard libraries in JavaScript offer no clue about which methods return a copy and which modify in place is, in my opinion, one of the language’s shortcomings, requiring unnecessary memorization.

Adding or Removing Single Elements at the Beginning or End

When we refer to the beginning (or front) of an array, we’re referring to the first element (element 0). Likewise, the end (or back) of an array is the largest element (if our array is arr, element arr.length-1). push and pop add and remove, respectively, elements to the end of the array (in place). shift and unshift remove and add, respectively, elements to the beginning of the array (in place).

NOTE

The names of these methods come from computer science terminology. push and pop are actions on a stack, where the important elements were the most recent ones added. shift and unshift treat the array like a queue, where the important elements are the oldest ones added.

push and unshift return the new length of the array after the new element has been added, and pop and shift return the element that was removed. Here are examples of these methods in action:

const arr = ["b", "c", "d"];

arr.push("e"); // returns 4; arr is now ["b", "c", "d", "e"]

arr.pop(); // returns "e"; arr is now ["b", "c", "d"]

arr.unshift("a"); // returns 4; arr is now ["a", "b", "c", "d"]

arr.shift(); // returns "a"; arr is now ["b", "c", "d"]

Adding Multiple Elements at the End

The concat method adds multiple elements to the array and returns a copy. If you pass concat arrays, it will break apart those arrays and add their elements to the original array. Examples:

const arr = [1, 2, 3];

arr.concat(4, 5, 6); // returns [1, 2, 3, 4, 5, 6]; arr unmodified

arr.concat([4, 5, 6]); // returns [1, 2, 3, 4, 5, 6]; arr unmodified

arr.concat([4, 5], 6); // returns [1, 2, 3, 4, 5, 6]; arr unmodified

arr.concat([4, [5, 6]]); // returns [1, 2, 3, 4, [5, 6]]; arr unmodified

Note that concat only breaks apart arrays you provide it directly; it does not break apart arrays inside those arrays.

Getting a Subarray

If you want to get a subarray from an array, use slice. slice takes up to two arguments. The first argument is where the subarray begins, and the second argument is where the subarray ends (which does not include the specified character). If you omit the end argument, it returns everything up to the end of the string. This method allows you to use negative indices to refer to elements with respect to the end of the string, which is handy. Examples:

const arr = [1, 2, 3, 4, 5];

arr.slice(3); // returns [4, 5]; arr unmodified

arr.slice(2, 4); // returns [3, 4]; arr unmodified

arr.slice(-2); // returns [4, 5]; arr unmodified

arr.slice(1, -2); // returns [2, 3]; arr unmodified

arr.slice(-2, -1); // returns [4]; arr unmodified

Adding or Removing Elements at Any Position

splice allows you to do in-place modification of the string, adding and/or removing elements from any index. The first argument is the index you want to start modifying; the second argument is the number of elements to remove (use 0 if you don’t want to remove any elements), and the remaining arguments are the elements to be added. Examples:

const arr = [1, 5, 7];

arr.splice(1, 0, 2, 3, 4); // returns []; arr is now [1, 2, 3, 4, 5, 7]

arr.splice(5, 0, 6); // returns []; arr is now [1, 2, 3, 4, 5, 6, 7]

arr.splice(1, 2); // returns [2, 3]; arr is now [1, 4, 5, 6, 7]

arr.splice(2, 1, 'a', 'b'); // returns [5]; arr is now [1, 4, 'a', 'b', 6, 7]

Cutting and Replacing Within an Array

ES6 brings a new method, copyWithin, that takes a sequence of elements from an array and copies them, in place, to a different part of the array, overwriting whatever elements are there. The first argument is where to copy to (the target), the second argument is where to start copying from, and the final (optional) argument is where to stop copying from. As with slice, you can use negative numbers for the start and end indexes, and they count backward from the end of the array. Examples:

const arr = [1, 2, 3, 4];

arr.copyWithin(1, 2); // arr is now [1, 3, 4, 4]

arr.copyWithin(2, 0, 2); // arr is now [1, 3, 1, 3]

arr.copyWithin(0, -3, -1); // arr is now [3, 1, 1, 3]

Filling an Array with a Specific Value

ES6 brings a welcome new method, fill, which allows you to set any number of elements with a fixed value (in place). This is particularly useful when used with the Array constructor (which allows you to specify the initial size of the array). You can optionally specify a start and end index if you only want to fill part of the array (negative indexes work as expected). Examples:

const arr = new Array(5).fill(1); // arr initialized to [1, 1, 1, 1, 1]

arr.fill("a"); // arr is now ["a", "a", "a", "a", "a"]

arr.fill("b", 1); // arr is now ["a", "b", "b", "b", "b"]

arr.fill("c", 2, 4); // arr is now ["a", "b", "c", "c", "b"]

arr.fill(5.5, -4); // arr is now ["a", 5.5, 5.5, 5.5, 5.5]

arr.fill(0, -3, -1); // arr is now ["a", 5.5, 0, 0, 5.5]

Reversing and Sorting Arrays

reverse is as simple as it gets; it reverses the order of the array in place:

const arr = [1, 2, 3, 4, 5];

arr.reverse(); // arr is now [5, 4, 3, 2, 1]

sort sorts an array in place:

const arr = [5, 3, 2, 4, 1];

arr.sort(); // arr is now [1, 2, 3, 4, 5]

sort also allows you to specify a sort function, which can come in quite handy. For example, there’s no meaningful way to sort objects:

const arr = [{ name: "Suzanne" }, { name: "Jim" },

{ name: "Trevor" }, { name: "Amanda" }];

arr.sort(); // arr unchanged

arr.sort((a, b) => a.name > b.name); // arr sorted alphabetically

// by name property

arr.sort((a, b) => a.name[1] < b.name[1]); // arr sorted reverse alphabetically

// by second letter of name property

TIP

In this example of sort, we’re returning a boolean. However, sort accepts a number as a return value. If you return a 0, sort will consider the two elements “equal,” and leave their order intact. This would allow you, for example, to sort alphabetically except for words starting with the letter k; everything would be sorted alphabetically, with all k words coming after all j words, and before all l words, but the k words would be in their original order.

Array Searching

If you want to find something in an array, you have a few choices. We’ll start with the humble indexOf, which has been available in JavaScript for some time. indexOf simply returns the index of the first element it finds that is strictly equal to what you’re looking for (there is a corresponding lastIndexOf that searches in the other direction, and returns the last index that matches what you’re looking for). If you only want to search a portion of an array, you can specify an optional start index. If indexOf (or lastIndexOf) returns –1, it wasn’t able to find a match:

const o = { name: "Jerry" };

const arr = [1, 5, "a", o, true, 5, [1, 2], "9"];

arr.indexOf(5); // returns 1

arr.lastIndexOf(5); // returns 5

arr.indexOf("a"); // returns 2

arr.lastIndexOf("a"); // returns 2

arr.indexOf({ name: "Jerry" }); // returns -1

arr.indexOf(o); // returns 3

arr.indexOf([1, 2]); // returns -1

arr.indexOf("9"); // returns 7

arr.indexOf(9); // returns -1

arr.indexOf("a", 5); // returns -1

arr.indexOf(5, 5); // returns 5

arr.lastIndexOf(5, 4); // returns 1

arr.lastIndexOf(true, 3); // returns -1

Next up is findIndex, which is similar to indexOf in that it returns an index (or –1 if there’s no match). findIndex is more flexible, though, in that you can provide a function that determines if an element is a match (findIndex doesn’t have an option to start at an arbitrary index, nor is there a corresponding findLastIndex):

const arr = [{ id: 5, name: "Judith" }, { id: 7, name: "Francis" }];

arr.findIndex(o => o.id === 5); // returns 0

arr.findIndex(o => o.name === "Francis"); // returns 1

arr.findIndex(o => o === 3); // returns -1

arr.findIndex(o => o.id === 17); // returns -1

find and findIndex are great if you’re looking for the index of an element. But what if you don’t care about the index of the element, but just want the element itself? find is like findIndex in that it allows you to specify a function to find what you’re looking for, but it returns the element itself instead of the index (or null if no such element was found):

const arr = [{ id: 5, name: "Judith" }, { id: 7, name: "Francis" }];

arr.find(o => o.id === 5); // returns object { id: 5, name: "Judith" }

arr.find(o => o.id === 2); // returns null

The functions that you pass to find and findIndex, in addition to receiving each element as their first argument, also receive the index of the current element and the whole array itself as arguments. This allows you to do things, for example, such as finding square numbers past a certain index:

const arr = [1, 17, 16, 5, 4, 16, 10, 3, 49];

arr.find((x, i) => i > 2 && Number.isInteger(Math.sqrt(x))); // returns 4

find and findIndex also allow you to specify what to use for the this variable during the function invocation. This can be handy if you want to invoke a function as if it were a method of an object. Consider the following equivalent techniques for searching for a Person object by ID:

class Person {

constructor(name) {

this.name = name;

this.id = Person.nextId++;

}

}

Person.nextId = 0;

const jamie = new Person("Jamie"),

juliet = new Person("Juliet"),

peter = new Person("Peter"),

jay = new Person("Jay");

const arr = [jamie, juliet, peter, jay];

// option 1: direct comparison of ID:

arr.find(p => p.id === juliet.id); // returns juliet object

// option 2: using "this" arg:

arr.find(p => p.id === this.id, juliet); // returns juliet object

You’ll probably find limited use for specifying the this value in find and findIndex, but it’s a technique that you’ll see later, where it is more useful.

Just as we don’t always care about the index of an element within an array, sometimes we don’t care about the index or the element itself: we just want to know if it’s there or isn’t. Obviously we can use one of the preceding functions and check to see if it returns –1 or null, but JavaScript provides two methods just for this purpose: some and every.

some returns true if it finds an element that meets the criteria (all it needs is one; it’ll stop looking after it finds the first one), and false otherwise. Example:

const arr = [5, 7, 12, 15, 17];

arr.some(x => x%2===0); // true; 12 is even

arr.some(x => Number.isInteger(Math.sqrt(x))); // false; no squares

every returns true if every element in the array passes the criteria, and false otherwise. every will stop looking and return false if it finds an element that doesn’t match the criteria; otherwise, it will have to scan the entire array:

const arr = [4, 6, 16, 36];

arr.every(x => x%2===0); // true; no odd numbers

arr.every(x => Number.isInteger(Math.sqrt(x))); // false; 6 is not square

Like all of the methods in this chapter that accept a method, some and every accept a second parameter that allows you to specify the value of this when the function is invoked.

The Fundamental Array Operations: map and filter

Of all the array operations, the ones you’ll find the most useful are map and filter. It’s really quite remarkable what you can accomplish with these two methods.

map transforms the elements in the array. To what? That’s the beauty: it’s up to you. Do you have objects that contain numbers, but you really just need the numbers? Easy. Does your array contain functions, and you need promises? Easy. Whenever the array is in one format and you need it in another, use map. Both map and filter return copies, and do not modify the original array. Let’s see some examples:

const cart = [ { name: "Widget", price: 9.95 }, { name: "Gadget", price: 22.95 }];

const names = cart.map(x => x.name); // ["Widget", "Gadget"]

const prices = cart.map(x => x.price); // [9.95, 22.95]

const discountPrices = prices.map(x => x*0.8); // [7.96, 18.36]

const lcNames = names.map(String.toLowerCase); // ["widget", "gadget"]

You may be wondering how lcNames is working: it doesn’t look like the others. All of the methods we’re discussing that take functions, including map, don’t care how you pass the function in. In the case of names, prices, and discountPrices, we’re constructing our own custom function (using the arrow notation). For lcNames, we’re using a function that already exists, String.toLowerCase. This function takes a single string argument and returns the lowercased string. We could as easily have written names.map(x ⇒ x.toLowerCase()), but it’s important to understand that a function is a function, no matter what form it takes.

When the function you provide gets called, it gets called for each element with three arguments: the element itself, the index of the element, and the array itself (which is seldom useful). Consider this example where we have our items and corresponding prices in two separate arrays, and we want to combine them:

const items = ["Widget", "Gadget"];

const prices = [9.95, 22.95];

const cart = items.map((x, i) => ({ name: x, price: prices[i]}));

// cart: [{ name: "Widget", price: 9.95 }, { name: "Gadget", price: 22.95 }]

This example is a little more sophisticated, but it demonstrates the power of the map function. Here, we’re using not just the element itself (x), but its index (i). We need the index because we want to correlate elements in items with elements in prices according to their index. Here, map is converting an array of strings to an array of objects, by pulling in information from a separate array. (Note that we had to surround the object in parentheses; without the parentheses, the arrow notation will take the curly braces to denote a block.)

filter, as the name implies, is designed to remove unwanted things from an array. Like map, it returns a new array with elements removed. What elements? Again, that’s completely up to you. If you guessed that we’re providing a function to determine which elements to remove, you’re catching on. Let’s see some examples:

// create a deck of playing cards

const cards = [];

for(let suit of ['H', 'C', 'D', 'S']) // hearts, clubs, diamonds, spades

for(let value=1; value<=13; value++)

cards.push({ suit, value });

// get all cards with value 2:

cards.filter(c => c.value === 2); // [

// { suit: 'H', value: 2 },

// { suit: 'C', value: 2 },

// { suit: 'D', value: 2 },

// { suit: 'S', value: 2 }

// ]

// (for the following, we will just list length, for compactness)

// get all diamonds:

cards.filter(c => c.suit === 'D'); // length: 13

// get all face cards

cards.filter(c => c.value > 10); // length: 12

// get all face cards that are hearts

cards.filter(c => c.value > 10 && c.suit === 'H'); // length: 3

Hopefully you can start to see how map and filter can be combined to great effect. For example, let’s say we want to create short string representation of the cards in our deck. We’ll use the Unicode code points for the suits, and we’ll use “A,” “J,” “Q,” and “Q” for ace and the face cards. Because the function that constructs this is a bit long, we’ll go ahead and create it as a separate function instead of trying to use an anonymous function:

function cardToString(c) {

const suits = { 'H': '\u2665', 'C': '\u2663', 'D': '\u2666', 'S': '\u2660' };

const values = { 1: 'A', 11: 'J', 12: 'Q', 13: 'K' };

// constructing values each time we call cardToString is not very

// efficient; a better solution is a reader's exercise

for(let i=2; i<=10; i++) values[i] = i;

return values[c.value] + suits[c.suit];

}

// get all cards with value 2:

cards.filter(c => c.value === 2)

.map(cardToString); // [ "2♥", "2♣", "2♦", "2♠" ]

// get all face cards that are hearts

cards.filter(c => c.value > 10 && c.suit === 'H')

.map(cardToString); // [ "J♥", "Q♥", "K♥" ]

Array Magic: reduce

Of all the array methods, my favorite is reduce. Where map transforms each element in the array, reduce transforms the entire array. It’s called reduce because it’s often used to reduce the array to a single value. For example, summing the numbers in an array or calculating the average is a way of reducing the array to a single value. However, the single value reduce provides can be an object or another array—as a matter of fact, reduce is capable of reproducing the functionality of map and filter (or, for that matter, any other array function we’ve discussed).

reduce—like map and filter—allows you to provide a function that controls the outcome. In the callbacks we’ve dealt with heretofore, the first element passed into the callback is always the current array element. Not so with reduce: the first value is an accumulator, which is what the array is being reduced to. The rest of the arguments are what you would expect: the current array element, the current index, and the array itself.

In addition to taking a callback, reduce takes an (optional) initial value for the accumulator. Let’s look at a simple example—summing the numbers in an array:

const arr = [5, 7, 2, 4];

const sum = arr.reduce((a, x) => a += x, 0);

The function being passed into reduce takes two parameters: the accumulator (a) and the current array element (x). In this example, we’re starting the accumulator off with a value of 0. Because this is our first experience with reduce, let’s walk through the steps JavaScript takes so we understand how it’s working:

1. The (anonymous) function is called for the first array element (5). a has the initial value 0, and x has the value 5. The function returns the sum of a and x (5), which will become the value of a in the next step.

2. The function is called for the second array element (7). a has the initial value 5 (passed in from the previous step), and x has the value 7. The function returns the sum of a and x (12), which will become the value of a in the next step.

3. The function is called for the third array element (2). a has the initial value 12, and x has the value 2. The function returns the sum of a and x (14).

4. The function is called for the fourth and final array element (4). a has the value 14, and x has the value 4. The function returns the sum of a and x (18), which is the return value of reduce (which is then assigned to sum).

The astute reader might realize that—in this very simple example—we don’t even need to assign to a; what’s important is what’s returned from the function (remember that the arrow notation doesn’t require an explicit return statement), so we could have just returned a + x. However, in more sophisticated examples, you might want to do more with the accumulator, so it’s a good habit to get into to modify the accumulator inside the function.

Before we move on to more interesting applications of reduce, let’s consider what happens if the initial accumulator is undefined. In this case, reduce takes the first array element as the initial value and starts calling the function with the second element. Let’s revisit our example, and omit the initial value:

const arr = [5, 7, 2, 4];

const sum = arr.reduce((a, x) => a += x);

1. The (anonymous) function is called for the second array element (7). a has the initial value 5 (the first array element), and x has the value 7. The function returns the sum of a and x (12), which will become the value of a in the next step.

2. The function is called for the third array element (2). a has the initial value 12, and x has the value 2. The function returns the sum of a and x (14).

3. The function is called for the fourth and final array element (4). a has the value 14, and x has the value 4. The function returns the sum of a and x (18), which is the return value of reduce (which is then assigned to sum).

You can see that there is one fewer step, but the outcome is the same. In this example (and in any case where the first element can serve as the initial value of the accumulator), we can benefit from omitting the initial value.

Using an atomic value (a number or string) as the accumulator is a common use for reduce, but using an object as an accumulator is a very powerful (and often overlooked) method. For example, if you have an array of strings, and you want to group the strings into alphabetic arrays (Awords, B words, etc.), you can use an object:

const words = ["Beachball", "Rodeo", "Angel",

"Aardvark", "Xylophone", "November", "Chocolate",

"Papaya", "Uniform", "Joker", "Clover", "Bali"];

const alphabetical = words.reduce((a, x) => {

if(!a[x[0]]) a[x[0]] = [];

a[x[0]].push(x);

return a; }, {});

This example is a little more involved, but the principle is the same. For every element in the array, the function checks the accumulator to see if it has a property for the first letter in the word; if not, it adds an empty array (when it sees "Beachball", there is no property a.B, so it creates one whose value is an empty array). It then adds the word to the appropriate array (which may have just been created), and finally, the accumulator (a) is returned (remember the value you return is used as the accumulator for the next element in the array).

Another example is computational statistics. For example, to calculate the mean and variation of a data set:

const data = [3.3, 5, 7.2, 12, 4, 6, 10.3];

// Donald Knuth's algorithm for calculating variance:

// Art of Computer Programming, Vol. 2: Seminumerical Algorithms, 3rd Ed., 1998

const stats = data.reduce((a, x) => {

a.N++;

let delta = x - a.mean;

a.mean += delta/a.N;

a.M2 += delta*(x - a.mean);

return a;

}, { N: 0, mean: 0, M2: 0 });

if(stats.N > 2) {

stats.variance = stats.M2 / (stats.N - 1);

stats.stdev = Math.sqrt(stats.variance);

}

Again, we’re leveraging an object as the accumulator because we need multiple variables (mean and M2, specifically: we could have used the index argument—minus one—in place of N if we had wanted to).

We’ll see one last (and very silly) example to drive home the flexibility of reduce by using an accumulator type we haven’t used yet—a string:

const words = ["Beachball", "Rodeo", "Angel",

"Aardvark", "Xylophone", "November", "Chocolate",

"Papaya", "Uniform", "Joker", "Clover", "Bali"];

const longWords = words.reduce((a, w) => w.length>6 ? a+" "+w : a, "").trim();

// longWords: "Beachball Aardvark Xylophone November Chocolate Uniform"

Here we’re using a string accumulator to get a single string containing all words longer than six letters. As a reader’s exercise, try rewriting this using filter and join (a string method) instead of reduce. (Start by asking yourself why we need to call trim after the reduce.)

Hopefully the power of reduce is dawning on you. Of all the array methods, it’s the most general and powerful.

Array Methods and Deleted or Never-Defined Elements

One behavior of the Array methods that often trips people up is the way they treat elements that have never been defined or have been deleted. map, filter, and reduce do not invoke the function for elements that have never been assigned or have been deleted. For example, before ES6, if you tried to be clever and initialize an array this way, you would be disappointed:

const arr = Array(10).map(function(x) { return 5 });

arr would still be an array with 10 elements, all undefined. Similarly, if you delete an element from the middle of an array, and then call map, you’ll get an array with a “hole” in it:

const arr = [1, 2, 3, 4, 5];

delete arr[2];

arr.map(x => 0); // [0, 0, <1 empty slot>, 0, 0]

In practice, this is seldom a problem because you’re normally working with arrays whose elements have been explicitly set (and unless you explicitly want a gap in an array, which is rare, you won’t use delete on an array), but it’s good to be aware of.

String Joining

Very often, you simply want to join the (string) value of the elements in an array together with some separator. Array.prototype.join takes a single argument, the separator (which defaults to a comma if you omit it), and returns a string with the elements joined together (including never-defined and deleted elements, which become empty strings; null and undefined also become empty strings):

const arr = [1, null, "hello", "world", true, undefined];

delete arr[3];

arr.join(); // "1,,hello,,true,"

arr.join(''); // "1hellotrue"

arr.join(' -- '); // "1 -- -- hello -- -- true --"

If used cleverly—and combined with string concatenation—Array.prototype.join can be used to create things like HTML <ul> lists:

const attributes = ["Nimble", "Perceptive", "Generous"];

const html = '<ul><li>' + attributes.join('</li><li>') + '</li></ul>';

// html: "<ul><li>Nimble</li><li>Perceptive</li><li>Generous</li></ul>";

Be careful you don’t do this on an empty array: you’ll end up with a single empty <li> element!

Conclusion

There’s a lot of power and flexibility built into the JavaScript’s Array class, but it can sometimes be daunting to know which method to use when. Tables 8-1 through 8-4 summarize Array functionality.

For Array.prototype methods that take a function (find, findIndex, some, every, map, filter, and reduce), the function that you provide receives the arguments shown in Table 8-1 for each element in the array.

Method

Description

reduce only

Accumulator (initial value, or return value of last invocation)

all

Element (value of current element)

all

Index of current element

all

Array itself (rarely useful)

Table 8-1. Array function arguments (in order)

All Array.prototype methods that take a function also take an (optional) value of this to be used when the functions are invoked, allowing you to invoke the function as if it were a method.

When you need to…

Use…

In-place or copy

Create a stack (last-in, first-out [LIFO])

push (returns new length), pop

In-place

Create a queue (first-in, first-out [FIFO])

unshift (returns new length), shift

In-place

Add multiple elements at end

concat

Copy

Get subarray

slice

Copy

Add or remove elements at any position

splice

In-place

Cut and replace within an array

copyWithin

In-place

Fill an array

fill

In-place

Reverse an array

reverse

In-place

Sort an array

sort (pass in function for custom sort)

In-place

Table 8-2. Array content manipulation

When you want to know/find…

Use…

The index of an item

indexOf (simple values), findIndex (complex values)

The last index of an item

lastIndexOf (simple values)

The item itself

find

Whether the array has an item that matches some criteria

some

Whether all elements in the array matches some criteria

every

Table 8-3. Array searching

When you want to…

Use…

In-place or copy

Transform every element in an array

map

Copy

Eliminate elements from an array based on some criteria

filter

Copy

Transform an entire array into another data type

reduce

Copy

Convert elements to strings and join together

join

Copy

Table 8-4. Array transformation