9.

Objects

So far, the types of values we’ve covered give us some flexibility but are still rather limiting as to the data we can model in our code. We don’t always want to think in terms of plain numbers, strings, or booleans. What if we want to model something more interesting, like a cat? Let’s say the properties that matter to us about cats are their age, name, colours, and sex. With what we’ve covered so far, we could write a cat’s data as follows.

const age = 9;
const name = 'Marvin';
const colours = ['black', 'white'];
const sex = 'female';

console.log(`My ${age} year old cat's name is ${name} and ${sex === 'female' ? 'she' : 'he'} is ${colours.join(' and ')}.`);

This works for such a trivial example, but what about when we want to include more than one cat’s information in an array? Or pass information about two cats to a function?

const cat1Age = 9;
const cat1Name = 'Marvin';
const cat1Colours = ['black', 'white'];
const cat1Sex = 'female';

const cat2Age = 2;
const cat2Name = 'Sassy';
const cat2Colours = ['grey'];
const cat2Sex = 'male';

const myCats = [
  [cat1Age, cat1Name, cat1Colours, cat1Sex],
  [cat2Age, cat2Name, cat2Colours, cat2Sex]
];

const printCats = cats => {
  const catNames = cats.map(cat => cat[1]).join(', ');
  console.log(`I have ${cats.length} cats. Their names are: ${catNames}.`);
};

printCats(myCats);

const catsSleeping = (name1, colours1, name2, colours2) => {
  console.log(`${name1} and ${name2} would enjoy sleeping on a carpet that is ${[...colours1, ...colours2].join(' and ')}.`);
};

catsSleeping(cat1Name, cat1Colours, cat2Name, cat2Colours);

Again, the above example works, but it should be obvious that this is not ideal. Having a cat be represented by a 4-element array of its properties means we must refer to the properties by their index number instead of their name. And the function would make more sense to take two arguments, the two cats, not four. When we think about these two cats, we are intuitively thinking about them as belonging to some cat type, and that’s exactly what objects help with.

Objects allow us to define our own custom types.

Using objects, we can imagine we have a cat type and model our cats as follows.

const cat1 = {
  age: 9,
  name: 'Marvin',
  colours: ['black', 'white'],
  sex: 'female'
};

const cat2 = {
  age: 2,
  name: 'Sassy',
  colours: ['grey'],
  sex: 'male'
};

const myCats = [cat1, cat2];

const printCats = cats => {
  const catNames = cats.map(cat => cat.name).join(', ');
  console.log(`I have ${cats.length} cats. Their names are: ${catNames}.`);
};

printCats(myCats);

const catsSleeping = (cat1, cat2) => {
  console.log(`${cat1.name} and ${cat2.name} would enjoy sleeping on a carpet that is ${[...cat1.colours, ...cat2.colours].join(' and ')}.`);
};

catsSleeping(cat1, cat2);

Now our code reads much more clearly! Each cat has named properties and the order of those properties doesn’t matter.

Making objects frees us from the predefined types. But it’s important to keep in mind that JavaScript does not enforce the object types that you define, in any way. It is up to you to think of them as new types and treat them as such. Making an object of a particular structure can be thought of as defining a new type. It can be thought of like this: if object A and object B have the exact same keys (not values), then A and B are the same type. In the above example, both cat1 and cat2 can be thought of as being the same cat type because they have all the same keys (in any order). If one of them had an extra key or was missing a key, even though they both still have cat-related information, they shouldn’t be considered the same type because they don’t share the same set of keys. This becomes especially important when dealing with an array of objects, where each object is expected to have the same keys otherwise errors can easily occur.

Creating

An object is comprised of key-value pairs. The keys are strings and their corresponding values can be any type (even objects!). Each key-value pair represents a property of the object. In a single line, an object representing a store product could look something like this:

{name: 'Spirit Island Board Game', price: 95.95, brand: 'Greater Than Games'}

Bigger objects are more pleasant to read when written on multiple lines:

{
  'name': 'Marvin',
  age: 9,
  colours: ['black', 'white'],
  sex: 'female',
  friendly: true
}

Syntactically, curly brackets enclose the entire object. Inside, we have key-value pairs separated by commas. In each key-value pair, the key and value are separated by a colon (keys on the left, values on the right). The keys can optionally have quotation marks because they are strings. The quotation marks are optional because keys are always strings.1

An object printed in the console may look slightly different than the same object in your code, but rest assured that if the general structure and syntax are the same, there is no logical difference.

console.log({name: 'Spirit Island Board Game', price: 95.95, brand: 'Greater Than Games'});

const myCat = {
  name: 'Marvin',
  age: 9,
  colours: ['black', 'white'],
  sex: 'female',
  friendly: true
};
console.log(myCat);

We can also use key names from variables containing strings, but we must use square brackets. Using the variables as values is straightforward.

const key1 = 'name';
const value1 = 'Marvin';
const key2 = 'age';
const value2 = 9;

const myCat = {
  [key1]: value1,
  [key2]: value2,
  colours: ['black', 'white'],
  sex: 'female',
  friendly: true
};
console.log(myCat);

Accessing properties

There are three ways to access the values of an object’s properties.

Dot notation

Dot notation is most similar to plain English. If product holds an object with a name property, we can use product.name to access the product’s name.

const product = {name: 'Spirit Island Board Game', price: 95.95, brand: 'Greater Than Games'};

console.log(product.name); // Spirit Island Board Game
console.log(product.price); // 95.95
console.log(product.brand); // Greater Than Games

Square bracket notation

Using square brackets is similar to using index numbers with arrays, only we’re dealing with strings instead of numbers.

const product = {name: 'Spirit Island Board Game', price: 95.95, brand: 'Greater Than Games'};

console.log(product['name']); // Spirit Island Board Game
console.log(product['price']); // 95.95
console.log(product['brand']); // Greater Than Games

This may seem like unnecessary syntax when we have the dot notation at our disposal. However, we must use square bracket notation when the key itself is stored a variable.

const product = {name: 'Spirit Island Board Game', price: 95.95, brand: 'Greater Than Games'};
const n = 'name';
const p = 'price';
const b = 'brand';

console.log(product[n]); // Spirit Island Board Game
console.log(product[p]); // 95.95
console.log(product[b]); // Greater Than Games

console.log(product.b); // undefined -- this tries to use a key named 'b' instead of the variable b

Destructuring

Sometimes we don’t want to give an object a name, but rather go straight to its properties. Destructuring allows us to create variables whose names are the exact names of keys of a given object.

const product = {name: 'Spirit Island Board Game', price: 95.95, brand: 'Greater Than Games'};

const {name, price, brand} = product; // Destructuring happens on the left

console.log(name); // Spirit Island Board Game
console.log(price); // 95.95
console.log(brand); // Greater Than Games

Notice that the destructuring looks the same as defining the object, but without the values.

This can come in handy when we’re defining a function that is not interested in all the properties of an object:

const myCat = {
  name: 'Marvin',
  age: 9,
  species: 'cat',
  colours: ['black', 'white'],
  sex: 'female',
  friendly: true
};

// Destructuring in the function arguments
const describeAnimal = ({name, species, colours}) => {
  const colourString = colours.join(' and ');
  console.log(`${name} is a ${colourString} ${species}.`);
};
const describeAnimalAlt = (animal) => {
  const {name, species, colours} = animal;
  const colourString = colours.join(' and ');
  console.log(`${name} is a ${colourString} ${species}.`);
};

describeAnimal(myCat);
describeAnimalAlt(myCat);

In the end, dot notation and object destructuring are simply shortcuts. We could do everything we need to only using square bracket notation. Choosing between the three comes down to personal preference in any scenario.

Updating properties

When we need to update a property’s value in an object, we can make a copy of the object and change only what we need to.

To copy an object, we can use the spread operator: ... .

const product = {name: 'Spirit Island Board Game', price: 95.95, brand: 'Greater Than Games'};
const productCopy = {...product};

console.log(product); // (the original)
console.log(productCopy); // (an exact copy)

This spreads out all of the object’s key-value pairs into a new object.

Let’s lower the price of our product object:

const product = {name: 'Spirit Island Board Game', price: 95.95, brand: 'Greater Than Games'};
const productSale = {...product, price: 47.97};

console.log(product); // (the original)
console.log(productSale); // (the product on sale)

It’s important that the spread comes first in the updated object, otherwise our desired change will be overridden.

We can update as many properties as we want in a single operation, even adding new properties:

const product = {name: 'Spirit Island Board Game', price: 95.95, brand: 'Greater Than Games'};
const newProduct = {...product, price: 79.95, name: 'Spirit Island', stock: 9};

console.log(product); // (the original)
console.log(newProduct); // (changed the price and name, and added stock)

Changing a value based on its previous value is quite common:

const product = {name: 'Spirit Island Board Game', price: 95.95, brand: 'Greater Than Games', stock: 9};
const moreProduct = {...product, stock: product.stock + 1};

console.log(product); // (the original)
console.log(moreProduct); // (increased stock by 1)

Methods

Objects have many methods to make use of.2 Here we will highlight a few of the most useful ones. Each of the following methods takes an object and returns an array. After all, we have plenty of ways of dealing with arrays.

Object.keys()

Get the keys of an object.

const product = {name: 'Spirit Island Board Game', price: 95.95, brand: 'Greater Than Games'};

console.log(Object.keys(product)); // [ 'name', 'price', 'brand' ]

Object.values()

Get the values of an object.

const product = {name: 'Spirit Island Board Game', price: 95.95, brand: 'Greater Than Games'};

console.log(Object.values(product)); // [ 'Spirit Island Board Game', 95.95, 'Greater Than Games' ]

Object.entries()

Get the key-value pairs of an object. Returns an array of pairs, where each pair is an array of two elements.

const obj = {a: 1, b: 2, c: 3};

console.log(Object.entries(obj)); // [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ]

Exercises


  1. Technically, object properties can be Symbols, but this is uncommon.↩︎

  2. See all the object methods on MDN.↩︎

Top