[flaviocopes] The JavaScript Fundamentals Course - JavaScript Objects

Objects

As introduced in the types section, any value that’s not of a primitive type (a string, a number, a boolean, a symbol, null or undefined) is an object . Even arrays or functions are, under the hoods, objects.

Here’s how we define an object:

const car = {

}

This is the object literal syntax, which is one of the nicest things in JavaScript.

You can also use the new Object syntax:

const car = new Object()

Another syntax is to use Object.create() :

const car = Object.create()

You can also initialize an object using the new keyword before a function with a capital letter. This function serves as a constructor for that object. In there, we can initialize the arguments we receive as parameters, to setup the initial state of the object:

function Car(brand, model) {
  this.brand = brand
  this.model = model
}

We initialize a new object using

const myCar = new Car('Ford', 'Fiesta')
myCar.brand //'Ford'
myCar.model //'Fiesta'

Objects have properties . Every property has a name and a value.

You might think an object is basically a map , or dictionary , data structure, and you would be correct.

The value of a property can be of any type, which means that it can even be an object, as objects can nest other objects.

When a property value is a function, we call it method .

Objects can inherit their properties from other objects, and we’ll see this in details when we’ll talk about inheritance.

Objects are always passed by reference .

If you assign a variable the same value of another, if it’s a primitive type like a number or a string, they are passed by value:

let age = 36
let myAge = age
myAge = 37
age //36
const car = {
  color: 'blue'
}
const anotherCar = car
anotherCar.color = 'yellow'
car.color //'yellow'

This is a key concept to understand, so play around with this.

Properties

I said that objects have properties, which are composed by a label associated with a value.

The object literal syntax we saw:

const car = {

}

lets us define properties like this:

const car = {
  color: 'blue'
}

here we have a car object with a property named color , with value blue .

Labels can be any string. Notice that I didn’t use quotes around color , but if I wanted to include a character not valid as a variable name in the property name, I would have had to:

const car = {
  color: 'blue',
  'the color': 'blue'
}

This means spaces, hyphens, and more special characters.

As you see, we separate each property with a comma.

Retrieving the value of a property

We can retrieve the value of a property using 2 different syntaxes.

The first is dot notation :

car.color //'blue'

The second, which is mandatory for properties with invalid names, is to use square brackets:

car['the color'] //'blue'

If you access an unexisting property, you get undefined :

car.brand //undefined

A nice way to check for a property value but default to a predefined value is to use the || operator:

const brand = car.brand || 'ford'

As said, objects can have nested objects as properties:

const car = {
  brand: {
    name: 'Ford'
  },
  color: 'blue'
}

You can access the brand name using

car.brand.name

or

car['brand']['name']

or even mixing:

car.brand['name']
car['brand'].name

Setting the value of a property

As you saw above you can set the value of a property when you define the object.

But you can always update it later on:

const car = {
  color: 'blue'
}

car.color = 'yellow'
car['color'] = 'red'

And you can also add new properties to an object:

car.model = 'Fiesta'

car.model //'Fiesta'

How to remove a property

Given the object

const car = {
  color: 'blue',
  brand: 'Ford'
}

you can delete a property from this object using

delete car.brand

Delete a property from an object in JavaScript

It works also expressed as:

delete car['brand']
delete car.brand
delete newCar['brand']

Setting a property to undefined

If you need to perform this operation in a very optimized way, for example, when you’re operating on a large number of objects in loops, another option is to set the property to undefined .

Due to its nature, the performance of delete is a lot slower than a simple reassignment to undefined , more than 50x times slower.

However, keep in mind that the property is not deleted from the object. Its value is wiped, but it’s still there if you iterate the object:

Iterate over the object

Using delete is still very fast, you should only look into this kind of performance issues if you have a very good reason to do so, otherwise it’s always preferred to have a more clear semantic and functionality.

Remove a property without mutating the object

If mutability is a concern, you can create a completely new object by copying all the properties from the old, except the one you want to remove:

const car = {
  color: 'blue',
  brand: 'Ford'
}
const prop = 'color'

const newCar = Object.keys(car).reduce((object, key) => {
  if (key !== prop) {
    object[key] = car[key]
  }
  return object
}, {})

How to count the number of properties in a JavaScript object

Use the Object.keys() method, passing the object you want to inspect, to get an array of all the (own) enumerable properties of the object.

Then calculate the length of that array by checking the length property:

const car = {
  color: 'Blue',
  brand: 'Ford',
  model: 'Fiesta'
}

Object.keys(car).length

I said enumerable properties. This means their internal enumerable flag is set to true, which is the default. Check MDN for more info on this subject.

How to check if a JavaScript object property is undefined

In a JavaScript program, the correct way to check if an object property is undefined is to use the typeof operator.

typeof returns a string that tells the type of the operand. It is used without parentheses, passing it any value you want to check:

const list = []
const count = 2

typeof list //"object"
typeof count //"number"
typeof "test" //"string"

typeof color //"undefined"

If the value is not defined, typeof returns the ‘undefined’ string .

Now suppose you have a car object, with just one property:

const car = {
  model: 'Fiesta'
}

This is how you check if the color property is defined on this object:

if (typeof car.color === 'undefined') {
  // color is undefined
}

Dynamic properties

When defining a property, its label can be an expression if wrapped in square brackets:

const car = {
  ['c' + 'o' + 'lor']: 'blue'
}

car.color //'blue'

Simpler syntax to include variables as object properties

Instead of doing

const something = 'y'
const x = {
  something: something
}

you can do this simplified way:

const something = 'y'
const x = {
  something
}

Methods

When used as object properties, functions are called methods:

const car = {
  brand: 'Ford',
  model: 'Fiesta',
  start: function() {
    console.log(`Started`)
  }
}

car.start()

Inside a method defined using a function() {} syntax we have access to the current object by referencing this :

const car = {
  brand: 'Ford',
  model: 'Fiesta',
  start: function() {
    console.log(`Started ${this.brand} ${this.model}`)
  }
}

car.start()

We don’t have access to this if we use an arrow function:

const car = {
  brand: 'Ford',
  model: 'Fiesta',
  start: () => {
    console.log(`Started ${this.brand} ${this.model}`) //not going to work
  }
}

car.start()

because arrow functions are not bound to the object. So, for object methods regular functions are the way to go.

Parameters

Methods can accept parameters, like regular functions:

const car = {
  brand: 'Ford',
  model: 'Fiesta',
  start: function(destination) {
    console.log(`Going to ${destination}`)
  }
}

car.start()

Dynamically select a method of an object in JavaScript

Sometimes you have an object and you need to call a method, or a different method, depending on some condition.

For example, you have a car object and you either want to drive() it or to park() it, depending on the driver.sleepy value.

If the driver has a sleepy level over 6, we need to park the car before it fells asleep while driving.

Here is how you achieve this with an if/else condition:

if (driver.sleepy > 6) {
  car.park()
} else {
  car.drive()
}

Let’s rewrite this to be more dynamic.

We can use the ternary operator to dynamically choose the method name, get it as the string value.

Using square brackets we can select it from the object’s available methods:

car[driver.sleepy > 6 ? 'park' : 'drive']

With the above statement we get the method reference. We can directly invoke it by appending the parentheses:

car[driver.sleepy > 6 ? 'park' : 'drive']()

Prototypal Inheritance

JavaScript is quite unique in the popular programming languages landscape because of its usage of prototypal inheritance.

As we saw in the previous lesson, JavaScript tried to “fake” a more popular approach to inheritance. But under the hoods nothing changed from the past.

While most object-oriented languages use a class-based inheritance model, JavaScript is based on the prototype inheritance model .

What does this mean?

Every single JavaScript object has a property, called prototype , which points to a different object.

This different object is the object prototype .

Our object uses that object prototype to inherit properties and methods.

Say you have an object created using the object literal syntax:

const car = {}

or one created with the new Object syntax:

const car = new Object()

in any case, the prototype of car is Object :

If you initialize an array, which is an object:

const list = []
//or
const list = new Array()

the prototype is Array .

You can verify this by checking with the Object.getPrototypeOf() and the Object.prototype.isPrototypeOf() methods:

const car = {}
const list = []

Object.getPrototypeOf(car) === Object.prototype
Object.prototype.isPrototypeOf(car)

Object.getPrototypeOf(list) === Array.prototype
Array.prototype.isPrototypeOf(list)

All the properties and methods of the prototype are available to the object that has that prototype:

Object.prototype is the base prototype of all the objects:

Object.getPrototypeOf(Array.prototype) == Object.prototype

If you wonder what’s the prototype of the Object.prototype, there is no prototype: it’s null . It’s a special snowflake :snowflake:.

The above example you saw is an example of the prototype chain at work.

I can make an object that extends Array and any object I instantiate using it, will have Array and Object in its prototype chain and inherit properties and methods from all the ancestors.

In addition to using the new operator to create an object, or using the literals syntax for objects and arrays, you can instantiate an object using Object.create() .

The first argument passed is the object used as prototype:

const car = Object.create({})
const list = Object.create(Array)

Pay attention because you can instantiate an array using

const list = Object.create(Array.prototype)

and in this case Array.isPrototypeOf(list) is false, while Array.prototype.isPrototypeOf(list) is true.

Classes

JavaScript has a quite uncommon way to implement inheritance: prototypal inheritance. We’ll see what that means later.

People coming from Java or Python or other languages had a hard time understanding the intricacies of prototypal inheritance, so the ECMAScript committee decided to sprinkle syntactic sugar on top of prototypical inheritance so that it resembles how class-based inheritance works in other popular implementations.

This is why, in 2015, the ECMAScript 6 (ES6) standard introduced classes.

This is important, it’s syntactic sugar: JavaScript under the hood didn’t change. It just added a nicer way to work with objects on top of the existing way.

A class definition

This is how a class looks.

class Person {
  constructor(name) {
    this.name = name
  }

  hello() {
    return 'Hello, I am ' + this.name + '.'
  }
}

A class has an identifier, which we can use to create new objects using new ClassIdentifier() .

When the object is initialized, the constructor method is called, with any parameters passed.

A class also has as many methods as it needs. In this case hello is a method and can be called on all objects derived from this class:

const flavio = new Person('Flavio')
flavio.hello()

Static methods

Normally methods are defined on the instance, not on the class.

Static methods are executed on the class instead:

class Person {
  static genericHello() {
    return 'Hello'
  }
}

Person.genericHello() //Hello

Class inheritance

Classes give JavaScript a more “classic” take on inheritance. Let’s describe it here, and then we’ll go into the “under the hoods” in the next lesson.

A class can extend another class, and objects initialized using that class inherit all the methods of both classes.

Inside a class, you can reference the parent class calling super() .

If the inherited class has a method with the same name as one of the classes higher in the hierarchy, the closest method takes precedence:

class Programmer extends Person {
  hello() {
    return super.hello() + ' I am a programmer.'
  }
}

const flavio = new Programmer('Flavio')
flavio.hello()

(the above program prints “ Hello, I am Flavio. I am a programmer. ”)

Classes do not have explicit class variable declarations, but you must initialize any variable in the constructor, although support for public class fields is in the works - and already in V8. JavaScript is always evolving.

Getters and setters

You can add methods prefixed with get or set to create a getter and setter, which are two different pieces of code that are executed based on what you are doing: accessing the variable, or modifying its value.

class Person {
  constructor(name) {
    this._name = name
  }

  set name(value) {
    this._name = value
  }

  get name() {
    return this._name
  }
}

If you only have a getter, the property cannot be set, and any attempt at doing so will be ignored:

class Person {
  constructor(name) {
    this._name = name
  }

  get name() {
    return this._name
  }
}

If you only have a setter, you can change the value but not access it from the outside:

class Person {
  constructor(name) {
    this._name = name
  }

  set name(value) {
    this._name = value
  }
}

Getters and setters are very useful when you want to execute some code upon changing the property value, or if you want to create a “computed” property. You can alter the values you return by using a getter.

You can also run some code, like logging to the console or to a file when a value is changed.

Values and entries

Object.values()

This method returns an array containing all the object own property values.

Usage:

const person = { name: 'Fred', age: 87 }
Object.values(person) // ['Fred', 87]

Object.values() also works with arrays:

const people = ['Fred', 'Tony']
Object.values(people) // ['Fred', 'Tony']

Object.entries()

This method returns an array containing all the object own properties, as an array of [key, value] pairs.

Usage:

const person = { name: 'Fred', age: 87 }
Object.entries(person) // [['name', 'Fred'], ['age', 87]]

Object.entries() also works with arrays:

const people = ['Fred', 'Tony']
Object.entries(people) // [['0', 'Fred'], ['1', 'Tony']]

Destructuring an object

Given an object, you can extract just some values and put them into named variables:

const person = {
  firstName: 'Tom',
  lastName: 'Cruise',
  actor: true,
  age: 54, //made up
}

const {firstName: name, age} = person

name and age contain the desired values.

The syntax also works on arrays:

const a = [1,2,3,4,5]
const [first, second] = a

This statement creates 3 new variables by getting the items with index 0, 1, 4 from the array a :

const [first, second, , , fifth] = a

Destructuring an object

Given an object, you can extract just some values and put them into named variables:

const person = {
  firstName: 'Tom',
  lastName: 'Cruise',
  actor: true,
  age: 54, //made up
}

const {firstName: name, age} = person

name and age contain the desired values.

The syntax also works on arrays:

const a = [1,2,3,4,5]
const [first, second] = a

This statement creates 3 new variables by getting the items with index 0, 1, 4 from the array a :

const [first, second, , , fifth] = a

Object.assign()

Object.assign() performs a shallow copy of an object, not a deep clone.

const copied = Object.assign({}, original)

Being a shallow copy, values are cloned, and objects references are copied (not the objects themselves), so if you edit an object property in the original object, that’s modified also in the copied object, since the referenced inner object is the same:

const original = {
  name: 'Fiesta',
  car: {
    color: 'blue'
  }
}
const copied = Object.assign({}, original)

original.name = 'Focus'
original.car.color = 'yellow'

copied.name //Fiesta
copied.car.color //yellow

Using the Object Spread operator

This ES2015 feature provides a very convenient way to perform a shallow clone, equivalent to what Object.assign() does.

const copied = { ...original }

Wrong solutions

Online you will find many suggestions. Here are some wrong ones:

Using Object.create()

Note: not recommended

const copied = Object.create(original)

This is wrong, it’s not performing any copy.

Instead, the original object is being used as the prototype of copied .

Apparently it works, but under the hoods it’s not:

const original = {
  name: 'Fiesta'
}
const copied = Object.create(original)
copied.name //Fiesta

original.hasOwnProperty('name') //true
copied.hasOwnProperty('name') //false

JSON serialization

Note: not recommended

Some recommend transforming to JSON:

const cloned = JSON.parse(JSON.stringify(original))

but that has unexpected consequences.

By doing this you will lose any Javascript property that has no equivalent type in JSON, like Function or Infinity . Any property that’s assigned to undefined will be ignored by JSON.stringify , causing them to be missed on the cloned object.

Also, some objects are converted to strings, like Date objects for example, (also, not taking into account the timezone and defaulting to UTC), Set, Map and many others:

JSON.parse(
  JSON.stringify({
    a: new Date(),
    b: NaN,
    c: new Function(),
    d: undefined,
    e: function() {},
    f: Number,
    g: false,
    h: Infinity
  })
)

This only works if you do not have any inner objects and functions, but just values.

Merging objects

ES2015 introduced the Spread Operator , which is the perfect way to merge two simple objects into one:

const object1 = {
  name: 'Flavio'
}

const object2 = {
  age: 35
}

const object3 = {...object1, ...object2 }

If both objects have a property with the same name, then the second object property overwrites the first.

The best solution in this case is to use Lodash and its merge() method, which will perform a deeper merge, recursively merging object properties and arrays.

See the documentation for it on the Lodash docs.

Quiz

Welcome to the quiz! Try to answer those questions, which cover the topics of this module.

You can also write the question/answer into the Discord chat, to make sure it’s correct - other students or Flavio will check it for you!

  • tell the various ways you can use to initialize an object
  • how can you set a property of an object? And how can you get its value?
  • what is a method? What’s the difference between a method and a function?
  • describe in your own words what is the prototype of an object
  • do you find classes more intuitive to use?
  • what is the useful thing about getters and setters?
  • what’s the main difference between Object.values() and Object.entries() ?
  • describe what object destructuring means and what is the result of a destructuring operation
  • shallow cloning vs deep cloning: what’s the difference?
  • how can we merge 2 objects?