Arrays and Objects
Arrays and Objects
By Chris Ferdinandi
Go Make Things, LLC
v5.1.0
Copyright 2022 Chris Ferdinandi and Go Make Things, LLC. All Rights Reserved.
Table of Contents
1. Intro
A quick word about browser compatibility
Using the code in this guide
2. Getting Array Items
Array Indexes
Getting an array item by its index
Array.indexOf()
Array.includes()
Array.find()
Array.findIndex()
3. Looping Over Arrays
for
for…of
Skipping and ending loops
Array.forEach()
4. Transforming Arrays
Array.map()
Array.filter()
Array.reduce()
Array.reverse()
Array.sort()
Array.join()
5. Add and Remove Array Items
Array.push()
Array.concat()
Array.slice()
Array.splice()
Array.shift()
Array.pop()
Array.from()
6. Getting Object Properties
Getting properties from an object
Multidimensional Objects
Optional Chaining
7. Looping Over and Transforming Objects
for…in
Object.keys()
Object.entries()
8. Add and Remove Object Properties
delete
Object.assign()
9. Immutability
Creating an immutable copy of array
Creating an immutable copy of an object
Immutable copies and multidimensional arrays and objects
A copy() helper function
Object.freeze()
Object.freeze() and multi-dimensional arrays and objects
A freeze() helper function
10. Spread Syntax
Passing an array of arguments into a function as individual arguments
Combine or copy an array or object
11. Destructuring Syntax
Array Destructuring
Object Destructuring
12. Object Property Shorthand Values
How object property shorthand values work
Object function shorthands
13. Putting it all together
Getting Setup
HTML
CSS
JavaScript
Creating a string from array data
Creating the basic markup
Creating the breeds markup
Creating a list of additional details
Creating a list of available breeds
Creating the breed summary markup
14. About the Author
Intro
In this guide, you’ll learn:
Array Indexes
Array items are referenced by their index, their numbered position within an array.
Arrays start at 0.
In the example above, turkey has an index of 0, and pb&j has an index of 3.
// returns "turkey"
sandwiches[0];
// returns "ham"
sandwiches[2];
Array.indexOf()
Get the index of an item in an array. If you’re not sure what an item’s index in an
array is, you can pass the item into Array.indexOf() to get it. It can also be
used to check if an item is in an array at all.
It returns the index of the item if it’s in the array, and-1 if it’s not.
// returns 0
sandwiches.indexOf('turkey');
// returns 3
sandwiches.indexOf('pb&j');
// returns -1
sandwiches.indexOf('grilled cheese');
Array.includes()
Check if an array includes an item. The Array.includes() method returns a
boolean: true if the array contains the item, and false if it does not.
// returns true
sandwiches.includes('turkey');
// returns false
sandwiches.includes('grilled cheese');
Array.find()
Get the first item in an array that satisfies a conditions you specify in a callback
function. If no match is found, it returns undefined.
The callback accepts an argument that represents the current item as the method
loops through the array. It should return a boolean value (true or false).
let sandwiches = ['turkey', 'tuna', 'ham', 'pb&j'];
// logs "turkey"
let turkey = sandwiches.find(function (sandwich) {
return sandwich === 'turkey';
});
console.log(turkey);
// logs "ham"
// "pb&j "also has 3 letters, but "ham" shows up first in
the array
let threeLetters = sandwiches.find(function (sandwich) {
return sandwich.length === 3;
});
console.log(threeLetters);
Array.findIndex()
Find the index of an item in a multidimensional array (an array whose items include
other arrays or objects).
Pass a callback function into the Array.findIndex() method. The callback itself
accepts three arguments: the current item in the loop, the index of the current
item in the loop, and the array itself. All three are optional, and you can name
them anything you want.
Inside the callback, you can check some conditions about the current item. The
Array.findIndex() method will return the index of the first item that you
return true for.
let sandwiches = [
{
name: 'turkey',
smelly: false
},
{
name: 'tuna',
smelly: true
},
{
name: 'pb&j',
smelly: false
}
];
for
Loop through arrays and array-like objects.
In the first part of the loop, before the first semicolon, we set a counter
variable (typically i, but it can be anything) to 0.
The second part, between the two semicolons, is the test we check against
after each iteration of the loop. In this case, we want to make sure the counter
value is less than the total number of items in our array. We do this by
checking the .length of our array.
Finally, after the second semicolon, we specify what to run after each loop. In
this case, we’re adding 1 to the value of i with i++.
We can then use i to grab the current item in the loop from our array.
for…of
Loop over iterable objects, including arrays.
In a for...of loop, you define a variable to represent the current item of the
iterable that you’re looping through. Inside the block (the stuff between curly
brackets), you can use that variable to reference the current item.
let sandwiches = ['turkey', 'tuna', 'ham', 'pb&j'];
// Skipping a loop
// logs "turkey", "tuna", "turkey", "pb&j"
for (let sandwich of sandwiches) {
console.log(sandwich);
// Breaking a loop
// logs "turkey"
for (let i = 0; i < sandwiches.length; i++) {
console.log(sandwiches[i]);
Array.forEach()
Loop over each item in an array and run a callback function on it.
You pass a callback function into the Array.forEach() method. The callback
itself accepts three arguments: the current item in the loop, the index of the
current item in the loop, and the array itself. All three are optional, and you can
name them anything you want.
let sandwiches = ['turkey', 'tuna', 'ham', 'pb&j'];
Unlike with for and for...of loops, you can’t end an Array.forEach()
callback function before it’s looped through all items. You can return to end the
current loop (like you would with continue), but there’s no way to break the
loop.
Because of that, I generally prefer using a for...of loop unless I explicitly need
the index.
// Skip "ham"
// logs 0, "tuna", 2, "turkey", 3, "pb&j"
sandwiches.forEach(function (sandwich, index) {
if (sandwich === 'ham') return;
console.log(index); // index
console.log(sandwich); // value
});
Transforming Arrays
How to transform array data.
Array.map()
Loop through each item in an array, transform it, and return a new array. Pass in a
callback function that accepts three arguments: the current item in the loop, its
index, and the array itself. All three are optional.
Whatever you return inside the callback function becomes the new value at that
index in the new array.
/**
* Double each number in an array
*/
/**
* Get an array of just names
*/
let data = [
{
name: 'Kyle',
occupation: 'Fashion Designer'
},
{
name: 'Liza',
occupation: 'Web Developer'
occupation: 'Web Developer'
},
{
name: 'Emily',
occupation: 'Web Designer'
},
{
name: 'Melissa',
occupation: 'Fashion Designer'
},
{
name: 'Tom',
occupation: 'Web Developer'
}
];
Array.filter()
Create a new array with only elements that pass a test you include as a callback
function. The callback accepts three arguments: the current item in the loop’s
value, its index, and the array itself. All three are optional.
let numbers = [1, 2, 7, 42, 99, 101];
Array.reduce()
Take the content of an array and return a single value. That value can be anything:
a string, number, object, or even another array.
The callback also accepts two arguments: the accumulator, which is the current
combined value, and the current item in the loop. Whatever you return is used as
the accumulator for the next item in the loop. On the very first loop, that starting
value is used instead.
/**
* Add all of the numbers in an array
*/
// logs 6
console.log(total);
/**
* Create a new array with only the names of wizards in
Huffepuff
*/
let wizards = [
{
name: 'Harry Potter',
house: 'Gryfindor'
},
{
name: 'Cedric Diggory',
house: 'Hufflepuff'
},
{
name: 'Tonks',
house: 'Hufflepuff'
},
{
name: 'Ronald Weasley',
house: 'Gryfindor'
},
{
name: 'Hermione Granger',
house: 'Gryfindor'
}
];
Array.reverse()
Reverse the order of items in an array.
// logs [5, 4, 3, 2, 1]
console.log(count);
Array.sort()
Sort and reorder the items in an array. It modifies the original array, and by default
will order the items alphanumerically.
You can optionally pass in a callback function that will modify the default sorting
behavior.
The Array.sort() method loops through each item, and passes two items at a
time as arguments into the callback function. You can compare those two items,
and return an integer telling Array.sort() what to do with them.
If you return -1, it will place the first item before the second one. If you return1, it
will move the second item before the current one. If you return 0 (or nothing at
all), it will leave them unchanged.
For example, let’s say you have an array of trees. Each item in the array is an object
with the type of tree, and the number pineCones it has.
let trees = [
{
type: 'White pine',
pineCones: 4
},
{
type: 'Blue spruce',
pineCones: 3
},
{
type: 'Douglas fir',
pineCones: 7
}
];
You want to sort the array so that the tree with the most pine cones is first, and the
one with the least pine cones is last.
});
Array.join()
Combine all items in an array into a string, separated by a delimiter that you can
pass in as an argument. By default, it will use a comma (,) as the delimiter if one is
not provided.
let messages = [
'I love Cape Cod potato chips.',
'What about you?'
];
// logs "I love Cape Cod potato chips. What about you?"
console.log(strWithSpace);
// logs "I love Cape Cod potato chips. =) What about you?"
console.log(strWithSmiley);
Add and Remove Array Items
How to add and remove items from an array.
Array.push()
Add items to an array. Pass the new items in as arguments.
Array.concat()
Merge two or more arrays together. Call Array.concat() on the first array, and
pass each array to merge with it in as arguments.
The first argument is the array index to start at, and the second is the index to end
on. Both are optional. If you omit the start index, it will start at the beginning. If
you omit the end index, it will go to the end.
To create a brand new copy of an array in its entirety, you can useslice() with no
arguments.
Array.splice()
Delete, replace, and add items to an array at specific indexes. The
Array.splice() method accepts three arguments: start, delete, and items.
The first, start, is the index of the item you want to modify in the array. It’s the
only required argument.
The second, delete, is the number of items to delete from the array. If you omit
this argument, the Array.splice() method will remove every item from the
start index on. If you set it to 0, it won’t remove any items.
Finally, if you want to insert one or more items into the array, you can pass them in
as additional arguments.
You can combine splice() with indexOf() to remove an item by it’s name.
// Remove "pb&j"
sandwiches.splice(sandwiches.indexOf('pb&j'), 1);
Array.shift()
Remove the first item from an array and returns it. The array is modified.
// logs "Gandalf"
console.log(first);
// logs "Merlin"
console.log(last);
Array.from()
Create a new array from an existing one, or transform an array-like object (like a
NodeList) into an array. Pass the array (or array-like object) to copy in as an
argument.
let lunch = {
sandwich: 'turkey',
drink: 'soda',
side: 'chips',
cookie: true
};
// Dot notation
// logs "turkey"
console.log(lunch.sandwich);
// Bracket notation
// also logs "turkey"
console.log(lunch['sandwich']);
If an object key includes characters other than letters and numbers (like dashes,
spaces, and so on), you have to use bracket notation. You also have to use bracket
notation if using a variable for the key value.
let weather = {
'Narragansett Beach': 'sunny',
miami: 'sunny',
portland: 'cloudy'
};
Multidimensional Objects
A multidimensional object is one that has nested arrays or objects for its property
values.
let lunch = {
sandwich: 'turkey',
drink: 'soda',
condiments: {
ketchup: false,
mustard: false,
mayo: true,
relish: false
},
toppings: ['tomato', 'lettuce']
};
To get items from multidimensional objects, you can use key notation, bracket
notation, or a combination of the two.
// logs true
console.log(lunch.condiments.mayo);
// logs "tomato"
console.log(lunch.toppings[0]);
Optional Chaining
If you attempt to get the property of an object that does not exist in a
multidimensional object, the browser will throw an error.
For example, imagine you had a wizard object, with a collection of spells. Each
one has a list of required materials, and the phrase you say to cast it.
let wizard = {
name: 'Merlin',
spells: {
levitate: {
materials: ['moss', 'a pinch of salt'],
phrase: 'Levioso!'
},
summon: {
materials: ['diamond dust', 'a drop of
water'],
phrase: 'Abracadabra!'
}
}
};
To get the phrase for the summon spell, you would do this.
// returns "Abracadabra!"
let summon = wizard.spells.summon.phrase;
But, if you try to get the phrase property for a spell that doesn’t exit, the browser
will throw an error.
// throws an error
// Uncaught TypeError: Cannot read property 'phrase' of
undefined
let teacups = wizard.spells.teacup.phrase;
To use optional chaining, add a question mark (?) before the dot (.) in your chain.
// returns "Abracadabra!"
let summonOptional = wizard?.spells?.summon?.phrase;
for…in
A for...in loop is similar to a for...of loop, but used to loop through objects.
The first part, key, is a variable that gets assigned to the object key on each loop.
The second part (in the example below, lunch), is the object to loop over.
let lunch = {
sandwich: 'ham',
snack: 'chips',
drink: 'soda',
desert: 'cookie',
guests: 3,
alcohol: false,
};
let lunch = {
sandwich: 'turkey',
chips: 'cape cod',
drink: 'soda'
};
Object.entries()
Return an array of key/value pairs from an object, also represented as arrays. Pass
in the object as an argument.
let lunch = {
sandwich: 'turkey',
chips: 'cape cod',
drink: 'soda'
};
delete
Remove an item from an object. Use the delete operator on the key to remove.
let lunch = {
sandwich: 'turkey',
chips: 'cape cod',
drink: 'soda'
};
Object.assign()
Perform a shallow merge of two or more objects into the first. Pass in each object to
merge as an argument.
Note: in a shallow merge, nested objects are overwritten completely rather than having
their values merged together.
let object1 = {
apple: 0,
banana: {
weight: 52,
price: 100
},
cherry: 97
};
let object2 = {
banana: {
price: 200
},
durian: 100
};
let object3 = {
apple: 'yum',
pie: 3.214,
applePie: true
};
All objects are merged into the first. To create a new object, pass in an empty object
as the first argument.
// Add a snack
lunch.snack = 'cookies';
A multidimensional array or object is one that has one or more nested arrays or
objects as property values.
// A multidimensional array
let wizards = [{
name: 'Radagast',
color: 'brown'
}, {
name: 'Gandalf',
color: 'gray'
}];
// A multidimensional object
let movies = {
studio: 'Pixar',
films: ['Soul', 'Onward', 'Up', 'WALL-E'],
directors: ['Brad Bird', 'Pete Docter', 'Andrew
Stanton'],
details: {
founded: '1986',
founders: ['Edwin Catmull', 'Alvy Ray Smith']
}
};
I created a copy() helper function that you can use to create immutable copies of
multidimensional arrays and objects.
It loops through each property in an object or array and creates a copy of it. If the
property is itself an array or object, the function repeats the process, creating a
unique immutable copy of it.
To use the helper function, include it in your code base, then pass the array or
object to copy in as an argument.
/*!
* Create an immutable clone of data (an array, object,
map, set, etc.)
* (c) 2021 Chris Ferdinandi, MIT License,
https://gomakethings.com
* @param {*} obj The data object to copy
* @return {*} The clone of the array or object
*/
function copy () {
// The helper function code...
}
Object.freeze()
Make an object or array itself immutable. Pass the array or object in as an
argument.
Once run, you can’t add, update, or delete items or properties from the array or
object.
let wizard = {
name: 'Merlin',
age: 'old AF'
};
If you want to edit a frozen array or object, you need to create a copy of using one of
the techniques above.
The wizard object is frozen or immutable, but the wizard.spells array is not.
I created a freeze() helper function that you can use to freeze multidimensional
arrays and objects.
It freezes any array or object you pass into it. Then, it loops through each property,
and if the property is itself an array or object, the function repeats the process,
freezing that array or and object and its properties as well.
To use the helper function, include it in your code base, then pass the array or
object to freeze in as an argument.
/*!
* Freeze a multi-dimensional array or object
* (c) 2021 Chris Ferdinandi, MIT License,
https://gomakethings.com
* @param {Array|Object} obj The array or object to freeze
* @return {Array|Object} The frozen array or object
*/
function freeze () {
// The helper function code...
}
let wizard = {
name: 'Merlin',
age: 'old AF',
spells: ['Disappear', 'Levitate', 'Heal']
};
The helper function returns the original object or array, so you can alternatively
freeze it and save it to a variable in one step.
Now, you can’t update the original object or any of its nested array or objects.
The spread operator can only be used inside of functions, arrays and objects. You
cannot use it on its own.
The spread operator can be really useful for some specific situations.
Instead of using bracket notation to get each number and pass it in individually,
you can use the spread operator to break the numbers array into individual items.
// Instead of this...
// returns 6
add(numbers[0], numbers[1]);
/**
* Arrays
*/
// Some arrays
let sandwiches1 = ['tuna', 'turkey', 'pb&j'];
let sandwiches2 = ['chicken', 'pb&j'];
// Copy an array
// Works like Array.from(sandwiches)
let sandwichesCopy = [...sandwiches1];
/**
* Objects
*/
// Some objects
let radagast1 = {
color: 'brown',
druid: true
};
let radagast2 = {
skills: 'Talks with animals',
druid: false
};
// Copy an object
// Works like Object.assign({}, radagst1);
let radagastCopy = {...radagast1};
For situations like this, the spread operator can provide a simpler syntax that
makes the intent of your code more clear and obvious.
These approaches aren’t inherently better than using methods like Array.from()
or Object.assign(), so ultimately, use whichever approach you find most
comfortable and easy to work with.
Destructuring Syntax
The destructuring syntax provides a way to pull array values and object properties
into individual variables.
Array Destructuring
Imagine you had an array of lunch items, and you wanted to pull them out into
individual variables for the entree, drink, side, and desert.
You define an array of variables, and the destructuring syntax will pull the values at
the matching indexes out and assign them to the variables.
// logs "chips"
console.log(side);
Object Destructuring
Object destructuring does the same thing as array destructuring, but with object
keys instead of array values.
For example, imagine you had an object with the best movies by movie studio, and
you wanted to pull them out into individual variables.
let movies = {
disney: 'Moana',
pixar: 'Up',
dreamworks: 'How to Train Your Dragon',
nickelodeon: 'Wonder Park'
};
You define an object of variables, and the destructuring syntax will pull the
properties at the matching keys out and assign them to the variables.
// logs "Up"
console.log(pixar);
You can also rename a variable to something different than its key in the object. In
your object variable, add a colon (:) and the new variable name you’d like to use.
You do not need to assign every key in an object to a variable. For example, if you
only wanted pixar and dreamworks, you would do this.
// Some details
let name = 'Merlin';
let job = 'wizard';
let age = 'old AF';
// The object
let wizard = {
name: name, // The old way
job, // ES6 shorthand
age // ES6 shorthand
};
Historically, we needed to do things like name: name. With ES6 shorthand values,
you can include the key name without the colon (:) or value.
Instead of creating a key name and then writing function () {}, you can add a
named function without the function keyword.
let wizard = {
// Values
name: name, // The old way
job, // ES6 shorthand
age, // ES6 shorthand
};
Putting it all together
To make this all tangible, let’s work on a project together. We’re going to display a
list of adoptable dogs for an animal rescue by taking a multidimensional array with
some pet data, manipulating it a bit, and using it to render HTML.
The starter template and complete project code are included in the source code on
GitHub.
Getting Setup
I’ve dropped some placeholder code into the template to get us started.
HTML
There’s really not much here. Just a heading and a <div> with the #dogs ID where
we’ll add our list of pets.
<h1>Adoptable Dogs</h1>
CSS
I’ve added just a little bit of styling to make the img element responsive, and add
some spacing between listings.
h2 {
margin-top: 2em;
}
img {
height: auto;
max-width: 100%;
}
JavaScript
Since this pocket guide is not about DOM injection, I added some starter JavaScript
to handle that so that you can focus on manipulating strings, arrays, and objects.
let petData = [
{
name: 'Rufus',
breeds: [
'Lab',
'German Shepard',
'Border Collie'
],
age: 'adult',
size: 'M',
gender: 'M',
details: ['No Cats', 'No Dogs'],
photo: 'img/rufus.jpg'
},
// ...
];
For this project, we’re going to focus on the getDogs() and getSummary()
functions.
The snippet above will result in a page that says Dog! Dog! Dog! Dog! Dog!.
Now we’re ready to start adding real markup.
<p>
Age: {age}<br>
Size: {size}<br>
Gender: {gender}<br>
Breeds: {breed1}, {breed2}
</p>
<strong>Other Details:</strong>
<ul>
<li>{detail1: ex. No Cats}</li>
</ul>
To get started, let’s copy/paste it into the petData.map() callback function, and
wrap it in a template literal (more on those in the Strings & Numbers pocket guide).
let getDogs = function () {
return petData.map(function (dog) {
return `
<h2>{Dog Name}</h2>
<p><img alt="A photo of {Dog Name}"
src="photo.jpg"></p>
<p>
Age: {age}<br>
Size: {size}<br>
Gender: {gender}<br>
Breeds: {breed1}, {breed2}
</p>
<strong>Other Details:</strong>
<ul>
<li>{detail1: ex. No Cats}</li>
</ul>`;
}).join('');
};
Next, let’s use object destructuring to assign the properties we’ll need in the dog
object to variables. This will make them a little bit easier to work with.
let getDogs = function () {
return petData.map(function (dog) {
<p>
Age: {age}<br>
Size: {size}<br>
Gender: {gender}<br>
Breeds: {breed1}, {breed2}
</p>
<strong>Other Details:</strong>
<ul>
<li>{detail1: ex. No Cats}</li>
</ul>`;
}).join('');
};
Now, we can start adding our destructured variables to the template literal string to
add their values. We’ll start with the basics: name, photo, age, size, and gender.
I’m also going to add a width property of 300 to the photo to prevent it from going
too wide.
let getDogs = function () {
return petData.map(function (dog) {
}).join('');
};
Fortunately, we can use the join() method to combine each item into a single
string.
We’ll pass in , as a delimiter. Dogs with just one breed won’t have any commas,
while dogs with multiple breeds will.
let getDogs = function () {
return petData.map(function (dog) {
}).join('');
};
These details are in a comma separated list, and we want to render them as a set of
list items.
We’ll again use the Array.map() and Array.join() methods to loop through
and create our markup, this time a string of list items.
let getDogs = function () {
return petData.map(function (dog) {
}).join('');
};
In our map() callback function, we’ll check to see if the detail has at least one
item has at least one character. If not, we’ll return a custom message instead of the
detail.
let getDogs = function () {
return petData.map(function (dog) {
}).join('');
};
We need to get an object with each of the breeds, and how many times each breed
shows up in our list of pet data. The Array.reduce() method is perfect for this.
Remember, the Array.reduce() method can be used to reduce a set of array data
down to a single thing, but it doesn’t have to be a string. It can create other arrays,
objects, and so on.
We’ll run the Array.reduce() method on the petData array to create an object.
We’ll pass in an empty object as the accelerator, which we’ll call breeds, and the
current dog in the loop. And we’ll return the breeds object on each loop.
// Get breeds
let breeds = petData.reduce(function (breeds, dog) {
}, {});
};
For each dog, we’ll loop through all of the dog.breeds with the
Array.forEach() method. We could also use a for...of loop here, but to show
a broader variety of approaches, lets use Array.forEach().
If the breed is already in our breeds object, we’ll take the existing value and
increase it by one. If not, we’ll set it to 1. When we’re done, we should have an
object where each key is a dog breed, and each value is how many of that breed are
available.
// Create a list of available breeds, and how many of each
breed there are
let getSummary = function () {
// Get breeds
let breeds = petData.reduce(function (breeds, dog) {
}, {});
};
We can use the Object.keys() method to get an array of keys from our object,
and length to check how many there are. If there are no breeds, we can return an
empty string.
// Create a list of available breeds, and how many of each
breed there are
let getSummary = function () {
// Get breeds
let breeds = petData.reduce(function (breeds, dog) {
// ...
}, {});
};
Then, we’ll use for...in to loop through each item in the breeds object. On
each loop, we’ll wrap the key, breed, in a list item (li). We’ll also add the value
for that key—the number of that breed that there are—and append the list item to
the summary string.
// Create a list of available breeds, and how many of each
breed there are
let getSummary = function () {
// Get breeds
let breeds = petData.reduce(function (breeds, dog) {
// ...
}, {});
};
Finally, we’ll add a heading and wrap our list items in an unordered list u
( l).
Hi, I’m Chris Ferdinandi. I believe there’s a simpler, more resilient way to make
things for the web.
I’m the author of the Vanilla JS Pocket Guide series, creator of the Vanilla JS
Academy training program, and host of the Vanilla JS Podcast. My developer tips
newsletter is read by thousands of developers each weekday.
I love pirates, puppies, and Pixar movies, and live near horse farms in rural
Massachusetts.
On my website at GoMakeThings.com.
By email at chris@gomakethings.com.
On Twitter at @ChrisFerdinandi.