Understanding JavaScript Array Methods

ยท

0 min read

Hashnode Bot Is there a "Share Draft" feature? Much needed! โœŒ๐Ÿฝ

โš  DRAFT: this is a draft. This line will be removed when the article is ready. โœŒ๐Ÿฝ โš 

DISCLAIMER: When I talk about array methods here, I am referring to only the ones that loop over the array.

The goals are to understand how they work and to discover how some can help making our code more expressive / meaningful.

The most common way to handle lists in JavaScript is with arrays.

We frequently want to loop over lists when processing our data. The for statement is the most natural way for developers, especially when coming from another laguage such as C or Java.

We often start by learning the for..i loop, which looks like follows.

const array = ["๐Ÿ", "๐ŸŒ", "๐Ÿ"];

const count = array.length;

for (let i = 0; i < count; i++) {
  const item = array[i];
  console.log(item);
}

To loop over the array we first need to know to total count of items it has, hence the count variable. The for..i loop advances over the items one by one, from index 0, the first one, to index (array.length - 1), the index of the last item.

Although it can be useful in some cases, for the most part working with the index is not necessary. When looping over an array we generally want to test all of them. And in most cases anyway the processing is not directly linked to the index.

There is a simpler for loop to iterate over our arrays (and other iterables as well): the for..of loop.

const array = ["๐Ÿ", "๐ŸŒ", "๐Ÿ"];

for (let fruit of array) {
  console.log(fruit);
}

This code looks much simpler. No need count the items nor to derrive the current one from it's index. Instead the loop gives us a variable for each iteration.

Array Methods

Working with arrays is so common that in JavaScript we have helper methods on arrays themselves. There are currently 30+ array methods and the number keeps growing as the language evolves.

A vast majority of them include some sort of looping over the array. Yes, there are helper methods that loop over arrays.

Let's have a loop at the forEach method. It might be the least useful in day-to-day use but the most appropriate (IMHO) to understand the concepts.

for..of vs. array.forEach()

Let's look again at our for..of loop from earlier. The code inside the curly braces (see the ๐Ÿ‘‰๐Ÿฝ and ๐Ÿ‘ˆ๐Ÿฝ comments) will run for each item in the array.

const array = ["๐Ÿ", "๐ŸŒ", "๐Ÿ"];

for (let fruit of array) /* here ๐Ÿ‘‰๐Ÿฝ */ {
  console.log(fruit);
} /*  ๐Ÿ‘ˆ๐Ÿฝ and here */

And each statement appears in the console on a separate line.

"๐Ÿ"
"๐ŸŒ"
"๐Ÿ"

We can acheive the same results with array.forEach. Here is what the code looks like.

const array = ["๐Ÿ", "๐ŸŒ", "๐Ÿ"];

array.forEach(
  function logFruit(fruit) /* here ๐Ÿ‘‰๐Ÿฝ */ {
    console.log(fruit);
  } /*  ๐Ÿ‘ˆ๐Ÿฝ and here */
);

Note that inside the curly braces of the callback function we have the same code present inside the curlies of the for..of loop.

As with the for..of loop, each statement appears in the console on a separate line.

"๐Ÿ"
"๐ŸŒ"
"๐Ÿ"

So What? ๐Ÿค”

We've acheived the same result with a different syntax. What's the point?! The point here was to understand the essence of what array methods do. Array methods take in a callback function which they execute over each item.

Unlike forEach, most of these methods can return a result. Let's have a look at the .map array method.

array.map()

The map() method is useful when we need to transform each item of the original one.

Say we want to add a label to each item of the array. This can be done imperatively with a for..of loop.

const array = ["๐Ÿ", "๐ŸŒ", "๐Ÿ"];

let labelled = [];

for (let fruit of array) {
  const labelledItem = `${fruit} is a fruit`;
  labelled = [...labelled, labelledItem];
}

console.log(labelled);

// Outputs:
// [ "๐Ÿ is a fruit", "๐ŸŒ is a fruit", "๐Ÿ is a fruit" ]

With .map it would look like this.

const array = ["๐Ÿ", "๐ŸŒ", "๐Ÿ"];

const labelled = array.map(function addLabel(fruit) {
  const labelledItem = `${fruit} is a fruit`;
  labelled = [...labelled, labelledItem];
});

console.log(labelled);

This approach is close to what we did before with .forEach, except we have returned a value on completion. Note that we assigned that value to a const, thus we can convey that we do not want this variable to be reassigned to another value.

Convey meaning

Another good side of this approach is that we pass in a callback function, to which we can give a name. This allows us to give more meaning to our code, it becomes self descriptive.

From this we can simplify our code and "hide" some implementation details without the reader loosing required information.

// our code ...
const array = ["๐Ÿ", "๐ŸŒ", "๐Ÿ"];

const labelled = array.map(addLabel);

console.log(labelled);

// somewhere else ...
function addLabel(fruit) {
  // ... implementation details
}

In the code above the reader has enough information about the labelled array. As the reader we know that it is based on array to which each item was added a label.

This information is enough to be able to understand what labelled is about and consuming it.

The implementation details of addLabel is only relevant when it has to be changed. A change request of a bug could be the reason for that.

Flexibility

This gives us some flexibility with our code.

// import addLabel from "./old-lib/addLabel.js";
import addLabel from "./current-lib/addLabel.js";

const array = ["๐Ÿ", "๐ŸŒ", "๐Ÿ"];

const labelled = array.map(addLabel);

console.log(labelled);

In this case addLabel is defined in a different file. Thus we can easily swap various implementations of the method without affecting the flow of our current file. The responsibility is given to an external file.