Arrays and Objects
— Confucius
Arrays
Let's say you're writing a task application and you need to store a bunch of tasks. You could declare a separate variable for every task like this:
const task1 = 'First task';
const task2 = 'Second task';
const task3 = 'Third task';
However, this would quickly become very tedious. Additionally, you probably want to be able to add or delete tasks in your application. Adding and deleting variables will become even more tedious. It would become almost as tedious as repeating the word tedious over and over. Did we mention that this is really tedious?
As you can see, we need a way to store multiple values in a single variable. We can do this with arrays.
A JavaScript array is an ordered collection of multiple values. You can declare an array using an array literal (also called an array initializer in this context):
const tasks = ['First task', 'Second task', 'Third task'];
Note that an array is no longer a primitive type.
Instead, arrays have the type object
:
console.log(typeof tasks); // object
You can access individual elements of an array using the index notation.
This works by writing the name of the array, followed by the position of the element you want to retrieve inside square brackets []
.
Note that when we count the indices (positions), we start at 0
, not at 1
:
console.log(tasks[0]); // First task
console.log(tasks[1]); // Second task
If the array index is too big, trying to access the element at that index will return undefined
:
console.log(tasks[3]); // undefined
You can get the length of an array using .length
:
console.log(tasks.length); // 3
JavaScript has elegant syntax for working with arrays. For example, if you want to assign variables based on values of an array, you would normally have to do something like this:
const firstTask = tasks[0];
const secondTask = tasks[1];
const thirdTask = tasks[2];
This is (you guessed it) tedious.
Instead, you can use the array destructuring assignment:
const [firstTask, secondTask, thirdTask] = tasks;
console.log(secondTask); // Second task
If you only care about some of the elements, you can use the spread (...
) syntax:
const [firstTask, ...otherTasks] = tasks;
Something that commonly trips up beginners is trying to copy an array.
Let's say you have an array of numbers called arr
and you want to create a copy called arr2
.
You would probably try something like this:
const arr = [1, 2, 3, 4];
const arr2 = arr;
This is wrong.
We can see that this is wrong if we try to change the first element of arr
and then have a look at arr[0]
and arr2[0]
:
arr[0] = 5;
console.log(arr[0]); // 5
console.log(arr2[0]); // 5
Uh-oh!
That's probably not what we want.
The reason for this behaviour is that arr
and arr2
both point to the same array.
Remember how we were careful to introduce a variable as a storage location together with a symbolic name?
Well, it turns out that different symbolic names may refer to the exact same storage location.
You can visualize it like this:
Here we have a storage location containing the values 1
, 2
, 3
and 4
somewhere.
We also have two symbolic names arr
and arr2
.
While the symbolic names are different, they point to the same storage location.
Therefore, if we change the storage location, we will observe a change via both symbolic names.
In order to actually copy the values, we can use the spread syntax again:
const copied = [...arr];
Let's check that this is indeed an actual copy:
arr[0] = 5;
console.log(arr[0]); // 5
console.log(copied[0]); // 1
This looks good. Here is the mental picture you should have in your head for copying an array:
If you only briefly skimmed the section on array destructuring and the spread syntax, go back again and read it carefully. These two concepts will come up a lot in the following chapters (much more often than you think right now).
Objects
Let's return to our imaginary (as of now) task application. A task will probably be something more than just a string. For example, it might contain an ID, a title and a description. We could, again, try to store these values in separate constants:
const taskId = 1;
const taskTitle = 'Read the Next.js book';
const taskDescription = 'Read and understand the Next.js book.';
As you can probably guess, this will quickly become tedious (oh no, not this again).
Objects to the rescue!
These allow us to store name-value pairs inside a single variable.
Here is how we might create a task
object that contains all the information we want to know about a task:
const task = {
id: 1,
title: 'Read the Next.js book',
description: 'Read and understand the Next.js book.',
};
Every such name-value pair is called a property.
We can access properties using the dot notation .
or the square bracket notation []
.
For example, we can access the title
property of the task
object by writing task.title
or task['title']
.
Try it out:
console.log(task.id); // 1
console.log(task.title); // Read the Next.js book
console.log(task.description); // Read and understand the Next.js book.
console.log(task['id']); // 1
console.log(task['title']); // Read the Next.js book
console.log(task['description']); // Read and understand the Next.js book.
Note that we will practically always use the dot notation.
Remember how you accessed the length of an array using arr.length
?
You can do that because every array has a property called length
that indicates the length of that array.
Properties don't have to be primitive values. They can also be other objects.
Generally speaking, you can arbitrarily nest objects and arrays. For example, here is how you can nest an object inside an object:
const user = {
name: 'John Doe',
task: {
id: 1,
title: 'Read the Next.js book',
description: 'Read and understand the Next.js book.',
},
};
You can access the title
property of the user.task
object like this:
console.log(user.task.title); // Read the Next.js book
If you try to access a property that doesn't exist, the result will be undefined
:
console.log(task.date); // undefined
Sometimes you want to explicitly indicate that a property may be absent. For example, a person may not have a task assigned to them. You can write something like this:
const person = {
name: 'John Doe',
task: undefined,
};
Instead of undefined
you can also null
which represents the absence of an object value.
Note that there is no separate null
data type.
Instead, null
is just a special object:
console.log(typeof null); // object
Here is how you can use null
to represent the absence of a property:
const person = {
name: 'John Doe',
task: null,
};
Whether to use undefined
or null
in this situation is largely convention.
Throughout this book we will always use undefined
.
Nevertheless, we want to emphasize that it's totally fine to use null
instead of undefined
in this situation.
Just pick a style and be consistent.
You can use the destructuring assignment when working with objects. This is similar to arrays:
const task = {
id: 1,
title: 'Read the Next.js book',
description: 'Read and understand the Next.js book.',
};
const { id, title, description } = task;
And just as with arrays, you can use the spread syntax with objects:
const taskWithAssignee = {
assignee: 'John Doe',
...task,
};
console.log(taskWithAssignee);
This will output:
{
assignee: 'John Doe',
id: 1,
title: 'Read the Next.js book',
description: 'Read and understand the Next.js book.'
}
Note that objects are more than just containers for values. We will return to this later.
Using const
with Arrays and Objects
There is often some confusion regarding the use of const
with arrays and objects.
For example, it seems strange that you can change the elements of a const
array:
const arr = [1];
arr[0] = 2; // Totally valid
arr.push(3); // Totally valid
It seems equally strange that you can change the properties of a const
object:
const obj = { prop: 1 };
obj.prop = 2; // Totally valid
Such assignments are possible because const
only applies to the constant itself, not to the contents of the constant.
This means that the only thing you can't do is to change what the constant is pointing to altogether.
For instance, this is not possible:
const arr = [1];
arr = [2, 3]; // Not valid
Similarly, this is also not possible:
const obj = { prop: 1 };
obj = { prop: 2 }; // Not valid
This means that const
is a pretty weak guarantee when working with arrays and objects.
After all, you often change elements of arrays and objects, but rarely change what the array and/or object is pointing to in its entirety.
Nevertheless, you should use const
even when working with arrays and objects.
A weak guarantee is better than no guarantee at all.