Step 5: JavaScript Arrays

April 30, 2021

It is a common requirement in programming languages to have a data structure that will be a container for other kinds of data. This is what arrays allow us to do. They are a container and collection of data. We can create arrays by using square brackets ([]). We can either create an empty array and populate it afterward or create it pre-populated with values. Here are some array examples:

const emptyArray = [];
const myFavoriteFood = ["pizza", "sushi", "steak"];
const myFavoriteNumbers = [3, 7, 19, 42];
const myFavoritePeople = [
  {
    name: "John Doe",
  },
  { name: "Jane Doe" },
];
const myFavoriteRandomThings = [42, "pizza", myFavoritePeople];

As we can see in the examples, an array can have any number of values, and they can be of any type. We separate different values in an array by using commas (,).

Arrays are ordered collection of data. This means that the position of the items in an array is stored and retained. We can access the items in an array by using the square bracket notation and a number that points to the position (index) of the item we are accessing.

const myFavoriteFood = ["pizza", "sushi", "steak"];
console.log(myFavoriteFood[1]);

Which item do you think the above example is going to display? You might intuitively guess that it is going to be pizza, but that is not correct. It would display sushi. Arrays are zero-indexed. The first position of an array is at index 0.

const myFavoriteFood = ["pizza", "sushi", "steak"];

console.log(myFavoriteFood[0] === "pizza");
console.log(myFavoriteFood[1] === "sushi");
console.log(myFavoriteFood[2] === "steak");
console.log(myFavoriteFood[3] === undefined);

We would get an undefined value if we try to access an index of an array that doesn't exist.

Using the square bracket notation we can also assign a new value to the given index of an array.

const myFavoriteFood = ["pizza", "sushi", "steak"];

console.log(myFavoriteFood[0] === "pizza");
console.log(myFavoriteFood[1] === "sushi");
console.log(myFavoriteFood[2] === "steak");

myFavoriteFood[0] = "tacos";

console.log(myFavoriteFood);

Arrays are Objects

Arrays are a subset of JavaScript objects. They have object-like behavior but are specialized to deal with collections of data.

Since arrays are objects, they have built-in methods and properties that we can use. If we wanted to add new items to an array, we would use the push method.

const myFavoriteFood = ["pizza", "sushi", "steak"];

myFavoriteFood.push("tacos");

console.log(myFavoriteFood);

push allows us to add new values to the end of an array. There is also a pop method that allows us to remove an item from the end of an array.

const myFavoriteFood = ["pizza", "sushi", "steak"];

myFavoriteFood.pop();

console.log(myFavoriteFood);

We frequently need to know how many items an array contains. We can use the length property of an array to get that information.

const myFavoriteFood = ["pizza", "sushi", "steak"];

console.log(myFavoriteFood.length);

Arrays have other methods and properties. We will learn about those other methods and properties when we actually need them.

Passed by Value vs Passed by Reference

We previously learned the difference between how different data types are passed around inside the program. Strings, numbers, and boolean values are passed by value. That means a new copy of them is created when they are assigned to a new variable.

const num = 42;
const newNum = num; // a copy of num gets created

This is not the case with objects. This is not the case with arrays either since arrays are a subset of objects too. Both objects and hence arrays are passed by reference.

let x = [42];
let y;

y = x;
x.push(7);

console.log(x);
console.log(y);

In this example, x and y would end up having the same values. The workaround for this issue is similar to how it is for objects. We need to use the spread operator (...) to create a copy of the array when assigning it to a different variable.

let x = [42];
let y;

y = [...x];
x.push(7);

console.log(x);
console.log(y);

Remember, just like in objects, the spread operator when used for arrays creates a shallow copy.

Strings as Collections

Arrays are collections of data. Strings are similar to arrays in the sense that they are collections of individual characters. This means that we can also use some of the array methods on strings as well.

const name = "John Doe";

const firstLetter = name[0];
console.log(firstLetter);

const nameLength = name.length;
console.log(nameLength);

However, we can't assign a new value to a character of a string. This operation below would not work on strings.

const name = "John Doe";

name[0] = "B";

console.log(name);

This is because strings are immutable. We can't change a string after it is created. If we want to update a string, we need to create a new copy of it. But we can change objects or arrays after we create them.

Example: Building a Password Validator

Let's bring everything that we have learned in this chapter together. We will write a password validator to check to see if a given string is suitable to be used as a password. The password requirements we are going to have is going be as follows:

  • Longer than 8 characters.
  • Shorter than 12 characters.
  • It needs to end with the character Z.

If the password is valid it should return true. If not, it should return false.

Clearly, these are some absurd requirements, but they will work well for our learning purposes. Let's start by creating the skeleton for our function.

function passwordValidator(password) {}

const myPassword = "";
console.log(passwordValidator(myPassword));

We are getting undefined when we execute this function since we are not returning anything from it yet.

Before we implement the first function, we will write a statement regarding our expectations from this function. We want it so that any password that is shorter than 8 characters is going to result in a false value, or else it would result in a true value.

function passwordValidator(password) {}

let myPassword = "1234"; // shorter than 8 characters
let result = passwordValidator(myPassword) === false;
console.log(result);

myPassword = "1234567Z"; // 8 characters.
result = passwordValidator(myPassword) === true;
console.log(result);

In short, we want both of the console.log statements in this code to display true after we are done implementing the first requirement. Let's do that.

function passwordValidator(password) {
  if (password.length < 8) {
    return false;
  }

  return true;
}

let myPassword = "1234"; // shorter than 8 characters
let result = passwordValidator(myPassword) === false;
console.log(result);

myPassword = "1234567Z"; // 8 characters.
result = passwordValidator(myPassword) === true;
console.log(result);

We can verify that our code is working since both of the console.log statements are displaying true.

Let's continue with a similar workflow, where we first write down what we expect to see and then implement the code that is going to satisfy that expectation.

function passwordValidator(password) {
  if (password.length < 8) {
    return false;
  }

  return true;
}

let myPassword = "1234"; // shorter than 8 characters
let result = passwordValidator(myPassword) === false;
console.log(result);

myPassword = "1234567Z"; // 8 characters.
result = passwordValidator(myPassword) === true;
console.log(result);

myPassword = "1234567890101"; // longer than 12 characters
result = passwordValidator(myPassword) === false;
console.log(result);

The last condition should display true after we implement the second requirement.

function passwordValidator(password) {
  if (password.length < 8) {
    return false;
  }

  if (password.length > 12) {
    return false;
  }

  return true;
}

let myPassword = "1234"; // shorter than 8 characters
let result = passwordValidator(myPassword) === false;
console.log(result);

myPassword = "1234567Z"; // 8 characters.
result = passwordValidator(myPassword) === true;
console.log(result);

myPassword = "1234567890101"; // longer than 12 characters
result = passwordValidator(myPassword) === false;
console.log(result);

Let's implement the last requirement by following the same methodology. First, we will write down a statement about the expectation from the implementation.

function passwordValidator(password) {
  if (password.length < 8) {
    return false;
  }

  if (password.length > 12) {
    return false;
  }

  return true;
}

let myPassword = "1234"; // shorter than 8 characters
let result = passwordValidator(myPassword) === false;
console.log(result);

myPassword = "1234567Z"; // 8 characters.
result = passwordValidator(myPassword) === true;
console.log(result);

myPassword = "1234567890101"; // longer than 12 characters
result = passwordValidator(myPassword) === false;
console.log(result);

myPassword = "123456789"; // does not have the 'Z' character at the end.
result = passwordValidator(myPassword) === false;
console.log(result);

And then we will handle the implementation of the requirement.

function passwordValidator(password) {
  const passwordLength = password.length;

  if (passwordLength < 8) {
    return false;
  }

  if (passwordLength > 12) {
    return false;
  }

  const lastCharacterIndex = passwordLength - 1;
  const lastCharacter = password[lastCharacterIndex];

  if (lastCharacter !== "Z") {
    return false;
  }

  return true;
}

let myPassword = "1234"; // shorter than 8 characters
let result = passwordValidator(myPassword) === false;
console.log(result);

myPassword = "1234567Z"; // 8 characters.
result = passwordValidator(myPassword) === true;
console.log(result);

myPassword = "1234567890101"; // longer than 12 characters
result = passwordValidator(myPassword) === false;
console.log(result);

myPassword = "123456789"; // does not have the 'Z' character at the end.
result = passwordValidator(myPassword) === false;
console.log(result);

We first created a variable called passwordLength to hold the password's length since it is being used in multiple places. Then, we are getting the index for the string's last character by subtracting one from this value. If a string is 8 characters long, then the last character would be index 7 since positions are zero-indexed.

const lastCharacterIndex = passwordLength - 1;

This operation gives us the last index position, and we can use this value to get the last character. We can then check to see if the last character is of the desired value. If so, we return true if not, and we return false.

We could have certainly written this code without having to write all those assertions about expected values. But then, we would still need to test the code against some password values to see if it works as expected. This way of working, where we first assert what we expect and then write the feature that would satisfy that assertion, is an example of Test-Driven Development. It is a commonly used software development methodology that results in code that is well tested and less susceptible to bugs.

Summary

In this chapter, we have learned about arrays. Arrays are ordered collections of data. We can use them to store other types of data, including other arrays. Arrays are a subset of objects. They have methods that we can use to manipulate the array, like push or pop, and properties such as length. Since arrays are objects, they are also passed by reference, so we should be careful when creating copies of arrays. We have also learned that strings are similar to arrays because they are also collections of items (characters).

We have also seen a simple example of Test-Driven Development (TDD). TDD is a programming methodology that is very frequently used in software development to develop applications that are less susceptible to bugs.

Find me on Social Media