Arrays are Objects

Surprise!

Every JavaScript array is also a JavaScript object

That means that arrays have properties and methods like any other object.

Examples:

See MDN: Array to see a lot more array methods

Iteration Methods

Every JavaScript array has a few very handy methods that let you apply a function to its contents.

method description returns
forEach do something to each item undefined
find find the first item that matches one matching item (or undefined if no match)
filter accept or reject each item a new collection, possibly smaller
map change each item into a new item a new collection of the same size
reduce scan the entire collection and "reduce" it to... ...a single result, e.g. a total

forEach

forEach works a lot like for..of, but using a callback function

Given this array of names...

let names = ['Alice', 'Bob', 'Carol', 'Charlie', 'David']
this code... and this code...
for (let name of names) {
  console.log(name.toUpperCase())
}
let printUpper = function(name) { 
  console.log(name.toUpperCase())
}
names.forEach(printUpper)

both print the same thing:

ALICE
BOB
CAROL
CHARLIE
DAVID

Find

to find the first item that matches the condition...

let names = ['Alice', 'Bob', 'Carol', 'Charlie', 'David'];
let beginsWithC = function(word) {
    return word.charAt(0).toUpperCase() === 'C';
};
let cName = names.find(beginsWithC) //=> 'Carol'

Note that:

Find Inline

For conciseness, people often define the filter function inline, like this:

names.find((word) => word.charAt(0).toUpperCase() === 'C')

Q: Is this more or less clear than the previous slide?

Lab: Find a Berry

Given the following array:

let fruits = ['Apple', 'Blueberry', 'Cherry', 'Date', 'Elderberry']

write some code that uses find to return the first item that ends with the string 'berry'

(in this case, 'Blueberry')

Filter

the filter iteration method returns all matching values, in a new array

let names = ['Alice', 'Bob', 'Charlie', 'Carol', 'David'];
let beginsWithC = function(word) {
    return word.charAt(0).toUpperCase() === 'C';
}
let cNames = names.filter(beginsWithC) //=> [ 'Charlie', 'Carol' ]

Lab: Find all Berries

Given the following array:

let fruits = ['Apple', 'Blueberry', 'Cherry', 'Date', 'Elderberry']

Now go find your code from the previous lab ("Find a Berry") and change it to use filter to return a new array containing all the fruits that end with the string 'berry'

Hint: all you need to do is change find to filter -- the matching function itself is the same.

Map

The map iteration method returns a new array whose elements correspond to the elements of the original array.

let names = ['Alice', 'Bob', 'Charlie', 'Carol', 'David'];
let upper = function(word) {
    return word.toUpperCase();
}
let bigNames = names.map(upper) //=> [ 'ALICE', 'BOB', 'CHARLIE', 'CAROL', 'DAVID' ]

It's called "map" because in a mathematical sense, it defines a mapping from one collection to another.

from to
'Alice' 'ALICE'
'Bob' 'BOB'
'Charlie' 'CHARLIE'
'Carol' 'CAROL'
'David' 'DAVID'

Lab: Titleize with Map

Remember the capitalize function? It capitalizes the first letter of a string and makes the whole rest of the string lowercase.

function capitalize(word) {
  let firstLetter = word[0];
  let restOfWord = word.slice(1);
  return firstLetter.toUpperCase() + restOfWord.toLowerCase();
}

Now please try to write a function that capitalizes each word in a string.

titleize("the rain in spain falls MAINLY on the PLAIN")
  //=> 'The Rain In Spain Falls Mainly On The Plain'

There is a solution on the next slide, but please try on your own first.

Hint: Inside your titleize function, you could call the existing capitalize function. Or you could "inline" the capitalization code. Or you could do something else!

Solution: Titleize

Here's one way to do it:

function titleize(phrase) {
  return phrase.split(' ').map((word) => capitalize(word)).join(' ');
}

Here's another, where the existing capitalize method is used as is as a mapping function:

function titleize(phrase) {
  return phrase.split(' ').map(capitalize).join(' ');
}

And another:

function titleize(phrase) {
    let words = [];
    let originalWords = phrase.split(' ');
    originalWords.forEach((word) => {
        words.push(capitalize(word))
    });
    return words.join(' ');
}

Whether to use method chaining is a very subjective aesthetic judgement. YMMV!

Reduce

The reduce method keeps track of a running total (aka accumulator or memo); whatever value the function returns is used as the accumulator for the next pass.

Here's some code that counts the total number of letters across all words in an array:

let names = ['Alice', 'Bob', 'Charlie', 'Carol', 'David'];
const reducer = function(accumulator, word) {
    return accumulator + word.length;
};
let totalCount = names.reduce(reducer, 0); //=> 25

The reduce algorithm can be difficult to follow at first; here's a walkthrough:

Iteration Accumulator In Word Length Accumulator Out
1 0 'Alice' 5 0 + 5 = 5
2 5 'Bob' 3 5 + 3 = 8
3 8 'Charlie' 7 8 + 7 = 15
4 15 'Carol' 5 15 + 5 = 20
5 20 'David' 5 20 + 5 = 25

See how the accumulator is used to pass information from one iteration to the next?

Iteration Methods in Emoji

map filter reduce in emoji

(image used with permission by @AccordionGuy based on a tweet by @steveluscher -- with a working implementation 😲 in Swift)

 Previous Lesson Next Lesson 

Outline

[menu]

/