0% found this document useful (0 votes)
13 views67 pages

Arrays and Objects

Uploaded by

bucevaldooo
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
0% found this document useful (0 votes)
13 views67 pages

Arrays and Objects

Uploaded by

bucevaldooo
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 67

Arrays & 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:

How to get and set values in arrays and objects.


How to loop over arrays and objects.
How to add and remove items from arrays and objects.
How to transform, manipulate, and copy arrays and objects.
What immutability is, why its important, and how to create immutable arrays
and objects.
How to use the ES6 spread syntax to copy and merge arrays and objects.
How to use array and object destructuring to more easily define variables from
array and object values.
How to use the object shorthand property to more easily assign properties and
functions in an object.

You can download all of the source code athttps://github.com/cferdinandi/arrays-


objects-source-code.

A quick word about browser compatibility


This guide focuses on methods and APIs that are supported in all modern browsers.
That means the latest versions of Edge, Chrome, Firefox, Opera, Safari, and the
latest mobile browsers for Android and iOS.

Using the code in this guide


Unless otherwise noted, all of the code in this book is free to use under the MIT
license. You can view of copy of the license at https://gomakethings.com/mit.

Let’s get started!


Getting Array Items
How to get and find items in arrays.

Array Indexes
Array items are referenced by their index, their numbered position within an array.
Arrays start at 0.

let sandwiches = ['turkey', 'tuna', 'ham', 'pb&j'];

In the example above, turkey has an index of 0, and pb&j has an index of 3.

Getting an array item by its index


You can get an item at a specific index using the following pattern:
arrayName[index].

let sandwiches = ['turkey', 'tuna', 'ham', 'pb&j'];

// 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.

let sandwiches = ['turkey', 'tuna', 'ham', 'pb&j'];

// 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.

let sandwiches = ['turkey', 'tuna', 'ham', 'pb&j'];

// 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
}
];

// Find the index of the tuna sandwich


// returns 1
sandwiches.findIndex(function (sandwich) {
if (sandwich.name === 'tuna') {
return true;
}
});
Looping Over Arrays
How to loop through arrays.

for
Loop through arrays and array-like objects.

let sandwiches = ['turkey', 'tuna', 'ham', 'pb&j'];

// logs 0, "tuna", 1, "ham", 2, "turkey", 3, "pb&j"


for (let i = 0; i < sandwiches.length; i++) {
console.log(i); // index
console.log(sandwiches[i]); // value
}

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'];

// logs "tuna", "ham", "turkey", "pb&j"


for (let sandwich of sandwiches) {
console.log(sandwich);
}

Skipping and ending loops


You can skip to the next item in a for or for...of loop using continue, or end
the loop altogether with break.
let sandwiches = ['turkey', 'tuna', 'ham', 'pb&j'];

// Skipping a loop
// logs "turkey", "tuna", "turkey", "pb&j"
for (let sandwich of sandwiches) {

// Skip to the next item in the loop


if (sandwich === 'ham') continue;

console.log(sandwich);

// Breaking a loop
// logs "turkey"
for (let i = 0; i < sandwiches.length; i++) {

// Skip to the next in the loop


if (sandwiches[i] === 'tuna') break;

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'];

// logs 0, "tuna", 1, "ham", 2, "turkey", 3, "pb&j"


sandwiches.forEach(function (sandwich, index) {
console.log(index); // index
console.log(sandwich); // value
});

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
*/

let numbers = [1, 4, 9];


let doubles = numbers.map(function(num) {
return num * 2;
});

// logs [2, 8, 18]


console.log(doubles);

/**
* 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'
}
];

let names = data.map(function (item) {


return item.name;
});

// logs ["Kyle", "Liza", "Emily", "Melissa", "Tom"]


console.log(names);

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];

// Create a new array with only numbers greater than 10


let biggerThanTen = numbers.filter(function (item) {
return item > 10;
});

// logs [42, 99, 101]


console.log(biggerThanTen);

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 Array.reduce() method accepts two arguments: a callback method to run


against each item in the array, and a starting value. Both are required.

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
*/

let numbers = [1, 2, 3];


let total = [1, 2, 3].reduce(function (sum, current) {
return sum + current;
}, 0);

// 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'
}
];

// This combines what you would otherwise do with map() and


filter() into one step
let hufflepuff = wizards.reduce(function (newArr, wizard) {
if (wizard.house === 'Hufflepuff') {
newArr.push(wizard.name);
}
return newArr;
}, []);

// logs ["Cedric Diggory", "Tonks"]


console.log(hufflepuff);
console.log(hufflepuff);

Array.reverse()
Reverse the order of items in an array.

let count = [1, 2, 3, 4, 5];

// Reverse the array order


count.reverse();

// 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.

let wizards = ['Merlin', 42, 'Gandalf', 2022, 'Radagast'];

// Sort the wizard array


// [2022, 42, "Gandalf", "Merlin", "Radagast"]
wizards.sort();

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.

In your callback function, you would compare tree1.pineCones to


tree2.pineCones. If tree1 has more pineCones, you would return -1 to put it
before tree2. Otherwise, you’d return 1 to put tree2 before tree1.
trees.sort(function (tree1, tree2) {

// If the tree1 has more pine cones, put it before


tree2
if (tree1.pineCones > tree2.pineCones) {
return -1;
}

// Otherwise, put tree2 before tree1


return 1;

});

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?'
];

let str = messages.join();


let strWithSpace = messages.join(' ');
let strWithSmiley = messages.join(' =) ');

// logs "I love Cape Cod potato chips.,What about you?"


console.log(str);

// 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.

let sandwiches = ['turkey', 'tuna', 'blt'];


sandwiches.push('chicken', 'pb&j');

// logs ["turkey", "tuna", "blt", "chicken", "pb&j"]


console.log(sandwiches);

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.

It returns a new array. The original arrays are not modified.

let sandwiches1 = ['turkey', 'tuna', 'blt'];


let sandwiches2 = ['chicken', 'pb&j'];
let allSandwiches = sandwiches1.concat(sandwiches2);

// logs ["turkey", "tuna", "blt"]


console.log(sandwiches1);

// logs ["chicken", "pb&j"]


console.log(sandwiches2);

// logs ["turkey", "tuna", "blt", "chicken", "pb&j"]


console.log(allSandwiches);
Array.slice()
Copy a segment of an array into a new array.

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.

The original array is not modified.

let sandwiches = ['turkey', 'tuna', 'chicken salad',


'italian', 'blt', 'grilled cheese'];

// ["chicken salad", "italian", "blt", "grilled cheese"]


let fewerSandwiches = sandwiches.slice(2);

// ["chicken salad", "italian", "blt"]


let fewerSandwiches2 = sandwiches.slice(2, 4);

To create a brand new copy of an array in its entirety, you can useslice() with no
arguments.

let sandwichesCopy = sandwiches.slice();

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.

let sandwiches = ['turkey', 'tuna', 'ham', 'pb&j'];

// Remove "ham" from the array


// It has an index of 2, and we only want to remove 1 item
sandwiches.splice(2, 1);

// Add "italian" between "tuna" and and "ham"


// Our target index is 2, we want to remove 0 items, and
add "italian"
sandwiches.splice(2, 0, 'italian');

// Replace "tuna" with "chicken salad"


// It has an index of 1, we want to remove 1 item, and add
"chicken salad"
sandwiches.splice(1, 1, 'chicken salad');

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.

let wizards = ['Gandalf', 'Radagast', 'Merlin'];


let first = wizards.shift();

// logs "Gandalf"
console.log(first);

// logs ["Radagast", "Merlin"]


console.log(wizards);
Array.pop()
Remove the last item from an array and returns it. The array is modified.

let wizards = ['Gandalf', 'Radagast', 'Merlin'];


let last = wizards.pop();

// logs "Merlin"
console.log(last);

// logs ["Gandalf", "Radagast"]


console.log(wizards);

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 sandwiches = ['turkey', 'tuna', 'ham', 'pb&j'];


let sandwichesCopy = Array.from(sandwiches);

The Array.from() method also accepts an optional second method, a callback


function that it will behave like the Array.map() method, transforming the
values in your object while creating an array.

// Get all of the button elements


let btns = document.querySelectorAll('button');

// Create an array of their text content


let btnsArr = Array.from(btns, function (btn) {
return btn.textContent;
});
Getting Object Properties
How to get properties in an object.

Getting properties from an object


There are two ways to get an item in an object: dot notation and bracket notation.

Bracket notation uses an objectName.keyName pattern. Bracket notation uses an


objectName['keyName'] pattern.

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']);

Dot notation is generally preferred.

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'
};

// This key can only be used with bracket notation


console.log(weather['Narragansett Beach']);

// You can use a variable for key with bracket notation,


too
let beach = 'miami';
console.log(weather[beach]);

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);

// also logs true


console.log(lunch.condiments['mayo']);

// also also logs true


console.log(lunch['condiments'].mayo);

You can get array items within a nested object, too.

// 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;

Optional chaining is a browser-native way to chain methods or properties, and


conditionally continue down the chain only if the value is not null or undefined.

To use optional chaining, add a question mark (?) before the dot (.) in your chain.
// returns "Abracadabra!"
let summonOptional = wizard?.spells?.summon?.phrase;

// returns undefined but does not throw an error


let teacupsOptional = wizard?.spells?.teacup?.phrase;

Instead of throwing an error, you’ll get back undefined.


Looping Over and Transforming Objects
How to loop over and transform objects.

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.

Here’s a working example.

let lunch = {
sandwich: 'ham',
snack: 'chips',
drink: 'soda',
desert: 'cookie',
guests: 3,
alcohol: false,
};

// logs "sandwich", "ham", "snack", "chips", "drink",


"soda", "desert", "cookie", "guests", 3, "alcohol", false
for (let key in lunch) {
console.log(key); // key
console.log(lunch[key]); // value
}

You can use continue and break in a for...in loop as well.

for (let key in lunch) {


if (key === 'drink') break;
console.log(lunch[key]);
}
Object.keys()
Return an array of keys from an object. Pass in the object as an argument.

let lunch = {
sandwich: 'turkey',
chips: 'cape cod',
drink: 'soda'
};

// logs ["sandwich", "chips", "drink"]


let keys = Object.keys(lunch);
console.log(keys);

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'
};

// logs [["sandwich", "turkey"], ["chips", "cape cod"],


["drink", "soda"]]
let entries = Object.entries(lunch);
console.log(entries);
Add and Remove Object Properties
How to add and remove properties from an object.

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'
};

// Remove the chips key from the lunch object


delete lunch.chips;

// logs {sandwich: 'turkey', drink: 'soda'}


console.log(lunch);

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
};

// In this example, "banana" will only contain {price: 200}


// In a deep merge, it would contain {price: 200, weight:
52}
let merged = Object.assign(object1, object2, object3);

All objects are merged into the first. To create a new object, pass in an empty object
as the first argument.

let mergedNewObj = Object.assign({}, object1, object2,


object3);

The Object.assign() method conducts a shallow merge. The deepAssign()


helper function follows the same conventions, but conducts a deep merge instead.
Immutability
In JavaScript, when you assign an existing array or object to a new variable, itdoes
not create a new array or object with the same properties. Instead, it creates a
reference to the original.

// Original array and object


let sandwiches = ['turkey', 'tuna', 'ham', 'pb&j'];
let lunch = {
sandwich: 'turkey',
chips: 'cape cod',
drink: 'soda'
};

// These create references to the original


let moreSandwiches = sandwiches;
let moreLunch = lunch;

// Remove "tuna" from sandwiches


// Remove "chips" from lunch
sandwiches.splice(1, 1);
delete lunch.chips;

// logs ["turkey", "ham", "pb&j"]


console.log(moreSandwiches);

// Logs {sandwich: "turkey", drink: "soda"}


console.log(moreLunch);

JavaScript provides a way to create a unique clone of an array or object: an


immutable copy. Updating the immutable copy does not change the values in the
original, and vice-versa.

Creating an immutable copy of array


You can create an immutable copy of an array with the Array.from() method, or
by using the Array.slice() method with no arguments.

// Create an immutable copy


let moreSandwiches = Array.from(sandwiches);
let evenMoreSandwiches = sandwiches.slice();

// Add a few sandwiches


sandwiches.push('italian', 'blt');

// logs ["turkey", "ham", "pb&j", "italian", "blt"]


console.log(sandwiches);

// logs ["turkey", "ham", "pb&j"]


console.log(moreSandwiches);
console.log(evenMoreSandwiches);

Creating an immutable copy of an object


You can create an immutable copy of an object using Object.assign(). Pass in an
empty object ({}) as the first argument and the object you want to copy as the
second.

// Create an immutable copy


let moreLunch = Object.assign({}, lunch);

// Add a snack
lunch.snack = 'cookies';

// Logs {sandwich: 'turkey', drink: soda, snack: 'cookies'}


console.log(lunch);

// Logs {sandwich: 'turkey', drink: soda}


console.log(moreLunch);
Immutable copies and multidimensional arrays and
objects
The approaches above—Array.from() and Object.assign()—work great for
simple arrays and objects. But they have some shortcomings when working with
multidimensional arrays and objects.

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']
}
};

With multidimensional arrays and objects, using Array.from() and


Object.assign() creates an immutable copy of the parent array or object only.

Any nested arrays or objects inside it are mutable.


// Create an immutable copy of the wizards array
let wizardsCopy = Array.from(wizards);

// Update a nested property


wizards[0].druid = true;

// logs {name: "Radagast", color: "brown", druid: true}


console.log(wizardsCopy[0]);

A copy() helper function

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...
}

// Create an immutable copy of wizards


let immutableWizards = copy(wizards);

// Update a nested property


wizards[0].druid = true;

// logs {name: "Radagast", color: "brown"}


// Here, the copy is unaffected by changes to the original
console.log(immutableWizards[0]);

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'
};

// Freeze the wizard object


Object.freeze(wizard);

// Try to make updates


// This will not work
wizard.age = 42;
wizard.wand = true;
delete wizard.name;

// logs {name: "Merlin", age: "old AF"}


console.log(wizard);

If you want to edit a frozen array or object, you need to create a copy of using one of
the techniques above.

let wizardClone = Object.assign({}, wizard);


wizardClone.age = 42;

Object.freeze() and multi-dimensional arrays and


objects
Just like with creating immutable copies, the Object.freeze() method does not
freeze nested arrays or objects inside a multi-dimensional object or array.
let wizard = {
name: 'Merlin',
age: 'old AF',
spells: ['Disappear', 'Levitate', 'Heal']
};

// Freeze the object


Object.freeze(wizard);

// Push an item to the nested spells array


wizard.spells.push('Dancing brooms');

// logs ["Disappear", "Levitate", "Heal", "Dancing brooms"]


console.log(wizard.spells);

The wizard object is frozen or immutable, but the wizard.spells array is not.

A freeze() helper function

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']
};

// Deep freeze the wizard object


freeze(wizard);

The helper function returns the original object or array, so you can alternatively
freeze it and save it to a variable in one step.

let wizard = freeze({


name: 'Merlin',
age: 'old AF',
spells: ['Disappear', 'Levitate', 'Heal']
});

Now, you can’t update the original object or any of its nested array or objects.

// These will not work


wizard.age = 42;
wizard.wand = true;
delete wizard.name;
wizard.spells.push('Dancing brooms');
Spread Syntax
The spread syntax operator (...) takes an array or object (or other iterable) and
expands its items into their own individual values.

let sandwiches = ['tuna', 'turkey', 'pb&j'];

// logs ["tuna", "turkey", "pb&j"]


console.log(sandwiches);

// logs tuna turkey pb&j


console.log(...sandwiches);

The spread operator can only be used inside of functions, arrays and objects. You
cannot use it on its own.

This, for example, would throw an error.

// Uncaught SyntaxError: Unexpected token '...'


...sandwiches;

The spread operator can be really useful for some specific situations.

Passing an array of arguments into a function as


individual arguments
Imagine you have a function, add(), that accepts two numbers as arguments and
adds them together.

// Add two numbers together


function add (num1, num2) {
return num1 + num2;
}

And, you have an array of numbers.


let numbers = [4, 2];

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]);

// You can do this...


// returns 6
add(...numbers);

Combine or copy an array or object


You can use the spread operator to combine or copy arrays or objects.

/**
* 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];

// Combine two arrays


// Works like sandwiches1.concat(sandwiches2)
let moreSandwiches = [...sandwiches1, ...sandwiches2];

/**
* 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};

// Combine two objects


// Works like Object.assign({}, radagast1, radagast2);
let moreRadagast = {...radagast1, ...radagast2};

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 could use bracket notation to get those items.

let lunch = ['turkey sandwich', 'soda', 'chips', 'cookie'];

let entree = lunch[0];


let drink = lunch[1];
let side = lunch[2];
let desert = lunch[3];

Destructuring provides a simpler way to do to the same thing.

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.

let [entree, drink, side, desert] = lunch;

// logs "turkey sandwich"


console.log(entree);

// 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.

You could use dot notation for that.

let movies = {
disney: 'Moana',
pixar: 'Up',
dreamworks: 'How to Train Your Dragon',
nickelodeon: 'Wonder Park'
};

let disney = movies.disney;


let pixar = movies.pixar;
let dreamworks = movies.dreamworks;
let nickelodeon = movies.nickelodeon;

Again, destructuring provides a simpler way to do to the same thing.

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.

let {disney, pixar, dreamworks, nickelodeon} = movies;

// 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.

For example, let’s change nickelodeon to nick.


let {disney, pixar, dreamworks, nickelodeon: nick} =
movies;

// logs "Wonder Park"


console.log(nick);

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.

let {pixar, dreamworks} = movies;

// logs "How to Train Your Dragon"


console.log(dreamworks);

// Uncaught ReferenceError: disney is not defined


console.log(disney);
Object Property Shorthand Values
The ES6 version of JavaScript introduced a new, simpler way to define properties
and functions in an object.

How object property shorthand values work


If you want to define a property in an object, and that key name already exists as a
variable within the object’s scope, you don’t have to explicitly assign a value. You
can use just the key name, and that variable’s value is used automatically.

// 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.

Object function shorthands


Let’s imagine we wanted to add a few functions to our wizard object. ES6 provides
a simpler way to do that as well.

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

// The old way of adding functions


summon: function () {
console.log('From out of thin air, watch me make a
bear');
},

// The ES6 shorthand way


vanish () {
console.log(`Now you see me, now you don't.`);
}

};
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>

<div id="dogs">Fetching our adoptable dogs...</div>

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.

First, there’s some dummy petData already in the source code.

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'
},
// ...
];

I’ve included two starter functions—getDogs() and getSummary()—that we’ll


use to generate the dog listing.
In both methods, I’ve provided a template for the markup. We’ll need to loop
through the dogs in the array, transform their data to create our markup, and
return it as a string.

// Create a list of available breeds, and how many of each


breed there are
function getSummary () {
// Template
// <h2>Available Breeds</h2>
// <ul>
// <li>{Breed Name} ({Breed Quantity})</li>
// <li>Ex. Lab (2)</li>
// </ul>
return '';
}

// Create a list of adoptable dogs


function getDogs () {
// Template
// <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>
return '';
}
The completed markup string gets injected into the DOM with theinnerHTML
property.

// Load list of adoptable dogs into the DOM


let dogList = document.querySelector('#dogs');
dogList.innerHTML = getSummary() + getDogs();

For this project, we’re going to focus on the getDogs() and getSummary()
functions.

Creating a string from array data


For this project, we need to loop through the petData array and create a string of
markup from it’s data.

In the getDogs() function, we’ll use Array.map() and Array.join() to do


that. The map() method will create a new array with our markup for each dog. The
join() method will combine all of the new array items into a single string.

let getDogs = function () {


return petData.map(function (dog) {
return 'Dog! ';
}).join('');
};

The snippet above will result in a page that says Dog! Dog! Dog! Dog! Dog!.
Now we’re ready to start adding real markup.

Creating the basic markup


Here’s the markup template we should work from (it’s included in the source code,
too).
<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>

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) {

// Assign properties to variables


let {name, photo, age, size, gender, breeds,
details} = dog;

// Create the HTML


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('');
};

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) {

// Assign properties to variables


let {name, photo, age, size, gender, breeds,
details} = dog;

// Create the HTML


return `
<h2>${name}</h2>
<p><img width="300" alt="A photo of ${name}"
src="${photo}"></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('');
};

Creating the breeds markup


Breeds is a bit less straightforward. We want a comma-separate listed, but the data
is an array.

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) {

// Assign properties to variables


let {name, photo, age, size, gender, breeds,
details} = dog;

// Create the HTML


return `
<h2>${name}</h2>
<p><img width="300" alt="A photo of ${name}"
src="${photo}"></p>
<p>
Age: ${age}<br>
Size: ${size}<br>
Gender: ${gender}<br>
Breeds: ${breeds.join(', ')}
</p>
<strong>Other Details:</strong>
<ul>
<li>{detail1: ex. No Cats}</li>
</ul>`;

}).join('');
};

Creating a list of additional details


Each dog has a list of additional details, like whether or not they’re good with other
dogs, good with cats, and so on.

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) {

// Assign properties to variables


let {name, photo, age, size, gender, breeds,
details} = dog;

// Create the HTML


return `
<h2>${name}</h2>
<p><img width="300" alt="A photo of ${name}"
src="${photo}"></p>
<p>
Age: ${age}<br>
Size: ${size}<br>
Gender: ${gender}<br>
Breeds: ${breeds.join(', ')}
</p>
<strong>Other Details:</strong>
<ul>
${details.map(function (detail) {
return `<li>${detail}</li>`;
}).join('')}
</ul>`;

}).join('');
};

At least one dog has no details.

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) {

// Assign properties to variables


let {name, photo, age, size, gender, breeds,
details} = dog;

// Create the HTML


return `
<h2>${name}</h2>
<p><img width="300" alt="A photo of ${name}"
src="${photo}"></p>
<p>
Age: ${age}<br>
Size: ${size}<br>
Gender: ${gender}<br>
Breeds: ${breeds.join(', ')}
</p>
<strong>Other Details:</strong>
<ul>
${details.map(function (detail) {
if (!detail.length) return '<li><em>No
additional details...</em></li>';
return `<li>${detail}</li>`;
}).join('')}
</ul>`;

}).join('');
};

Creating a list of available breeds


Now let’s jump over to the getSummary() function.

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.

// 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) {

// return breeds to next loop


return breeds;

}, {});

};

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) {

// Loop through each breed


dog.breeds.forEach(function (breed) {
// If breed already exists, increase count
// Otherwise, create it
if (breeds[breed]) {
breeds[breed] = breeds[breed] + 1;
} else {
breeds[breed] = 1;
}
});

// return breeds to next loop


return breeds;

}, {});

};

Creating the breed summary markup


Now that we have an object of breeds, let’s create our markup.

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) {
// ...
}, {});

// If there are no breeds, return an empty string


if (!Object.keys(breeds).length) {
return '';
}

};

Next, we’ll create a variable, summary, to hold our markup string.

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) {
// ...
}, {});

// If there are no breeds, return an empty string


if (Object.keys(breeds).length < 1) {
return '';
}

// Create summary markup


let summary = '';
for (let breed in breeds) {
summary += `<li>${breed} (${breeds[breed]})</li>`;
}

// Return the markup


return `
<h2>Available Breeds</h2>
<ul>
${summary}
</ul>`;

};

Finally, we’ll add a heading and wrap our list items in an unordered list u
( l).

Congratulations! You just created a dynamic UI by manipulating and transforming


arrays and objects.
About the Author

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.

You can find me:

On my website at GoMakeThings.com.
By email at chris@gomakethings.com.
On Twitter at @ChrisFerdinandi.

You might also like