In JavaScript, one of the most importance pieces of data that you'll work with is the object. Objects are known as key/value pairs in JavaScript.

const pizza = {
  crust: 'thin',
  cheese: 'mozzarella',
  sauce: 'red',
  meats: ['sausage', 'pepperoni'],
};

An object is denoted by using a pair of {} curly braces. Here, a key is cheese and its value is mozzarella. Keys are defined by giving a name followed by a : colon like crust:. Values are passed as any JavaScript data type.

In JavaScript, data types are how we describe the types of data in our code. JavaScript's data types include: Boolean (true/false), Null (no value), Undefined (declared but no value), Number (integers and floats), String, and Symbol (a unique value). Though not recognize as a data types but data structures, Objects and Arrays can also be used as values.

Together, these make up a key value pair. After each key/value pair in our object, we add a comma. Technically, the last key/value pair in your object doesn't require a trailing comma, but many developers add it as a stylistic preference.

In this example, we've created an object by storing it in a variable called pizza. This represents a single-level object which means that this object doesn't have any nested keys or properties (more on this below).

Accessing the keys or properties of an object

In order to access the keys (also referred to as properties) of an object, there are two techniques that we can use: dot notation and bracket notation.

Using dot notation

Dot notation is the most common syntax for accessing object keys. This syntax is used when you know the name of the key that you want to access, like this:

console.log(pizza.crust); // Logs 'thin'

Here, we take the name of the variable where our object is stored pizza and access the crust property by placing a dot . between the variable name and the property we'd like to access. So it's clear, if we wanted to know what meats were on our pizza, we'd do this:

console.log(pizza.meats); // Logs ['sausage', 'pepperoni']

Using bracket notation

Another method you can use for accessing properties on objects is bracket notation.

console.log(pizza['cheese']) // Logs 'mozzarella'

Here, instead of a dot . we use [] brackets with a String between them containing the name of the property we'd like to access on our pizza object, 'cheese'. Here, we can think of pizza like a target we're shooting at. The brackets represent our sights and in this example 'cheese' is in our sights.

Typically, bracket notation is reserved for two cases: when our key/property contains - hyphens, or, when the key/property we need to access is variable (meaning it could change to something we don't know).

const pizza = {
  crust: 'thin',
  cheese: 'mozzarella',
  sauce: 'red',
  meats: ['sausage', 'pepperoni'],
  'is-available': true
};

console.log(pizza['is-available']); // Logs true

Two things to note here. First, we've added a new property with a hyphen in it is-available. Notice that when we do this, we wrap the key/property with quotes. This is required. If you omit these, JavaScript will throw an error when your code is executed.

Second, notice that here, we're utilizing bracket notation again, but this time it's mandatory. If we were to write this code:

console.log(pizza.is-available); // Syntax error

We would get a syntax error and our code would not work.

Using bracket notation with variables

One of the neat parts about bracket notation is that it can be used with variables. In the event that the name of property we want to access will change, we can pass the name of the property as a variable using bracket notation:

Object.keys(pizza).forEach((ingredient) => {
  console.log(ingredient, pizza[ingredient]); // Logs each key of our object followed by its value.
});

Here, we use a built-in JavaScript method called Object.keys() which gives us back all of the keys/properties of the object we pass it as an array, like this:

console.log(Object.keys(pizza)); // Logs ['crust', 'cheese', 'sauce', 'meats', 'is-available']

Next, we take that array and "loop over it" using the .forEach() array method. For each item in our array, we call the arrow function passed to forEach and give it an argument ingredient which represents the current item in the array we're looping over. For example, on the first "loop" we'd get crust, then cheese, then sauce, and so on.

Because we know that ingredient's value will change based on the current position of our loop, we use bracket notation to say pizza[ingredient] which translates to "give us the value of the key/property on the pizza object that matches the current item we're looping over." So, thin, mozzarella, red, and so on.

Nested objects

Where objects can become equally powerful and unwieldy if you're not careful is in their ability to be nested.

const pizza = {
  crust: 'thin',
  cheese: 'mozzarella',
  sauce: 'red',
  meats: ['sausage', 'pepperoni'],
  'is-available': true,
  prices: {
    small: 8.99,
    medium: 11.99,
    Large: 13.99,
  }
};

Here, we've taken our pizza object and have extended it with an additional property prices which contains another nested object. Just like we saw above, we can access the properties of that nested object, too. Like this:

console.log(pizza.prices.small); // Logs 8.99

In the case of our .forEach() example above, with our loop example, we'd log out the following:

console.log(ingredient, pizza[ingredient]); // Logs prices { small: 8.99, medium: 11.99, large: 13.99 }

Interesting! While we do get a value back, notice that we get the entire nested prices object.

Names are important! Notice that when we started, our object's keys/properties represented the ingredients on our pizza. By adding prices, the name ingredient starts to lose meaning. Instead, it'd be good to change the name to something more generic like attribute.

What's neat about nesting is that you can go as many levels deep as you'd like. But be careful! Past two levels, not only are nested objects tricky to deal with in your code, they can also pose an issue when you try to store them in a database.

Copying data between objects with Object.assign()

In some cases, creating a copy of an object will be necessary. This is typical when you're using an object to represent a template of something, but want to override the defaults with more specific values:

const pizza = {
  crust: 'thin',
  cheese: 'mozzarella',
  sauce: 'red',
  meats: ['sausage', 'pepperoni'],
  'is-available': true,
  prices: {
    small: 8.99,
    medium: 11.99,
    Large: 13.99,
  }
};

const newPizza = Object.assign(pizza, { crust: 'regular' });
console.log(newPizza); /* Logs:

const pizza = {
  crust: 'regular',
  cheese: 'mozzarella',
  sauce: 'red',
  meats: ['sausage', 'pepperoni'],
  'is-available': true,
  prices: {
    small: 8.99,
    medium: 11.99,
    Large: 13.99,
  }
};
*/

Using Object.assign() we can pass a target object as the first argument to Object.assign() and a source object (an object with changes or additions we'd like to make to the target object) as the second argument.

Here, because crust already exists on our target object, pizza, passing it as part of the source object forces JavaScript to overwrite the original thin value on the newPizza value returned by Object.assign(). Similarly, if we want to add an additional value to our object:

const pizza = {
  crust: 'thin',
  cheese: 'mozzarella',
  sauce: 'red',
  meats: ['sausage', 'pepperoni'],
  'is-available': true,
  prices: {
    small: 8.99,
    medium: 11.99,
    Large: 13.99,
  }
};

const newPizza = Object.assign(pizza, { vegetables: ['mushrooms', 'olives', 'green-peppers'] });
console.log(newPizza); /* Logs:

const pizza = {
  crust: 'regular',
  cheese: 'mozzarella',
  sauce: 'red',
  meats: ['sausage', 'pepperoni'],
  'is-available': true,
  prices: {
    small: 8.99,
    medium: 11.99,
    Large: 13.99,
  },
  vegetables: ['mushrooms', 'olives', 'green-peppers'],
};
*/

In this example, because vegetables didn't exist before, our newPizza value is identical to our original pizza, however, we've added the vegetables array that was passed as part of the source object we assigned to our target object, pizza.

Copying data between objects with the spread operator

While object assign is handy, another way that we can copy data between objects in JavaScript is using the spread operator syntax introduced in the ES9/ES2018 version of JavaScript.

const pizza = {
  crust: 'thin',
  cheese: 'mozzarella',
  sauce: 'red',
  meats: ['sausage', 'pepperoni'],
  'is-available': true,
  prices: {
    small: 8.99,
    medium: 11.99,
    Large: 13.99,
  }
};

const newPizza = {
  ...pizza,
  crust: 'regular',
};

console.log(newPizza); /* Logs:

const pizza = {
  crust: 'regular',
  cheese: 'mozzarella',
  sauce: 'red',
  meats: ['sausage', 'pepperoni'],
  'is-available': true,
  prices: {
    small: 8.99,
    medium: 11.99,
    Large: 13.99,
  }
};
*/

Neat! Just like we saw with Object.assign(), in this example, we create our newPizza object by hand, and "copy" the contents of our original pizza object by adding it as a key/property (without a : colon or value) on our newPizza object, proceeded by three dots ..., or, the spread operator.

Here, the end result is identical to what we saw with Object.assign() above. First we specify we want to copy the contents of pizza, but then, because we place crust: 'regular' underneath it, we end up overwriting the crust value defined on pizza. Cool! Can you guess what it looks like to add new keys/properties?

const pizza = {
  crust: 'thin',
  cheese: 'mozzarella',
  sauce: 'red',
  meats: ['sausage', 'pepperoni'],
  'is-available': true,
  prices: {
    small: 8.99,
    medium: 11.99,
    Large: 13.99,
  }
};

const newPizza = {
  ...pizza,
  vegetables: ['mushrooms', 'olives', 'green-peppers'],
};

console.log(newPizza); /* Logs:

const pizza = {
  crust: 'thin',
  cheese: 'mozzarella',
  sauce: 'red',
  meats: ['sausage', 'pepperoni'],
  'is-available': true,
  prices: {
    small: 8.99,
    medium: 11.99,
    Large: 13.99,
  },
  vegetables: ['mushrooms', 'olives', 'green-peppers'],
};
*/

Yup! Pretty cool, right? While you will need to use this code in either a web browser or Node.js environment where the ... syntax is supported, it's a much cleaner way to combine together objects without a lot of fuss!

Using destructuring and ...rest

One final bit about objects to share is the concept of destructuring. This is a way to access the properties of an object, by "plucking off" the properties that you want from an object and creating new variables with the names of the properties:

const pizza = {
  crust: 'thin',
  cheese: 'mozzarella',
  sauce: 'red',
  meats: ['sausage', 'pepperoni'],
  'is-available': true,
  prices: {
    small: 8.99,
    medium: 11.99,
    large: 13.99,
  }
};

const { crust, cheese, sauce, meats, ...rest } = pizza;

console.log(crust); // Logs 'thin'
console.log(cheese); // Logs 'mozzarella'
console.log(sauce); // Logs 'red'
console.log(meats); // Logs ['sausage', 'pepperoni']
console.log({ ...rest }); // Logs { 'is-available': true, prices: { small: 8.99, medium: 11.99, large: 13.99 } }

See what's happening? In order to "destructure" an object, we start by defining a variable with const followed by a set of {} curly braces. Inside those curly braces, we specify the names of the keys/properties that we want to access on our object. After the closing } curly brace, we type = pizza to say "we want to destructure (pluck off) these from this."

When we do this, JavaScript will create variables for us automatically using the names of the keys/properties that we destructured. So, here, we get crust, cheese, sauce, and meats as variables as though we declared them individually.

But what about that ...rest one? What's up there? Well, like the name implies, that's saying "now that you've plucked off these keys, give me the rest of them." By "give" we mean collect them into a variable called rest. Here, we use the same ... three dot syntax from above. In this context, this is know as the "rest" syntax (you'll commonly see these dots referred to as the rest/spread syntax).

The ... essentially works like a vacuum and "sucks up" anything we didn't destructure. In case you're wondering, the ...rest is optional when restructuring an object. Generally, it's most helpful when you need to separate properties from an object without disturbing others.

Objects!

Objects are an incredible tool to have when building software with JavaScript. As you learn the language, they're worth spending a good amount of time studying as you will use them in nearly every piece of code you write.

For some weird reason it feels like it's time for pizza...