Prototype, this stranger!

A guide to objects, classes, and inheritance in JavaScript

Davide D'Antonio
JavaScript in Plain English

--

Photo by Vipul Jha on Unsplash

In this article we will see how to use objects, classes and inheritance in JavaScript. In particular we will discuss the concept of “prototype” and how it can help us solve particular problems related to Object Oriented Programming in JavaScript.

General concepts

Class, objects, and inheritance are very important concepts in object-oriented programming. In its classic version, the basic concepts are basically three:

  • Class: an abstraction of the real world with which we can represent, for example, a person, a thing etc. A class defines a state (attributes) and a behavior (methods).
  • Object: it’s the specific instance of a certain class, the one that is returned to us when we use the new operator. It has its own specific state and behavior.
  • Inheritance: represents a mechanism that allows you to create an object (child) based on another already defined (parent).

Graphically we can schematize the concepts just expressed with a very simple example:

The Cat and Dog classes inherit the behaviors from the Mammal class, while c1, c2 are instances of the Cat class and d1, d2 are instances of the Dog class.

JavaScript objects

Anything in JavaScript that is not a primitive type is an object. An object is a set of key-value pairs, where the values can in turn be objects, primitive variables or functions. If an object has multiple key-value pairs, they must be separated by a comma. You can declare an object in two ways, the first is to use the Object constructor and the second is to declare a variable in which the key-value pairs are enclosed by {}. Let’s see how to declare an object with a small example:

const student = {
name: 'Davide',
number: '123456'
}
const classroom = new Object({
name: 'Computer Science I',
teacher: 'Dennis Ritchie'
})
console.log(student)
console.log(classroom)

If you run this piece of code the result will be

{ name: 'Davide', number: '123456' }
{ name: 'Computer Science I', teacher: 'Dennis Ritchie' }

You can access to the object properties using the dot .:

const student = {
name: 'Davide',
number: '123456'
}
const classroom = new Object({
name: 'Computer Science I',
teacher: 'Dennis Ritchie'
})
console.log(`Student: ${student.name}`)
console.log(`Teacher: ${classroom.teacher}`)

In this case the result will be:

Student: Davide
Teacher: Dennis Ritchie

Define object properties

There are several ways to define the properties of an object in JavaScript. The first is to specify the object name followed by: a dot, the name of the new property, an equal sign and the value of the new property:

const student = {
name: 'Davide',
number: '123456'
}
student.birthday = '4th July'
console.log(student)

Obviously, if we run this code, the result will be:

{ name: 'Davide', number: '123456', birthday: '4th July' }

Combining properties between objects is a common practice. Doing it property by property is boring. The static function Object.assign helps us to do this with a few lines of code. Let’s see how it works with a small example:

const obj = {}
const student = {
name: 'Davide',
number: '123456'
}
Object.assign(obj, student, {
birthday: '4th July'
})
console.log(obj)

Also in this case the result we will get by running the script will be:

{ name: 'Davide', number: '123456', birthday: '4th July' }

The function iterates all the properties of the objects passed in input starting from the last one and assigns them to the object given in input previously. So in the example above, the birthday property is assigned to the student object and after the properties of the student object are assigned to obj. If the property we are assigning exists in the leftmost object, passed as a parameter, this will be overwritten with the new one. If we modify the script as follows:

const obj = {
birthday: '2nd July'
}
const student = {
name: 'Davide',
number: '123456',
birthday: '3rd July'
}
Object.assign(obj, student, {
birthday: '4th July'
})
console.log(obj)

It’s no wonder that the result we will get by running this script will be the same as that obtained in the previous examples:

{ name: 'Davide', number: '123456', birthday: '4th July' }

Another way to define an object property is to use the static function Object.assign . It allows us to define not only the name of the property and possibly its value, but it provides us with a series of options that can be useful on the property we are defining. In particular, the function takes three parameters as input:

  • The object to which you want to add a property;
  • The name of the property;
  • An options object.

With the options object passed as the third parameter we can specify the following properties:

  • configurable: if set to true the property can be deleted or modified. Its default value is false.
  • enumerable: if set to true, the property will be visible during the enumeration of properties on the object (for example for..in or Object.keys()). The default value is false;
  • value: the value we want to assign to the property. Its default value is undefined;
  • writable: if set to true the property can be overwritten. Its default value is false;
  • get: a function representing the method that returns the value of the added property. Its default value is undefined;
  • set: A function representing the method that sets the value of the added property. Its default value is undefined;

Let’s see how to use this function with some practical examples. We define a new birthday property on our student object. This property will only be readable once defined and will not be editable so, in addition to giving an initial value to the value property, we also set the writable property to false:

const student = {
name: 'Davide',
number: '123456'
}
Object.defineProperty(student, 'birthday', {
writable: false,
value: '4th July'
})
console.log(student) // [1]
console.log(student.birthday) // [2]
// [3]
student.birthday = 'Another date'
console.log(student.birthday)

Running the script you will get a run-time error such as:

TypeError: Cannot assign to read only property ‘birthday’ of object ‘#<Object>’

Let’s analyze the three points highlighted in the script:

  • We have not set the enumerable property, then the property will have the default value of false, consequently when we try to print the entire object on the screen with console.log, the birthday property and its relative value will not be displayed.
  • The second console.log will correctly print the value of the birthday property on the screen.
  • We have not set the value of the writable option to false, the birthday property cannot be overwritten, as a result we will get an error at run time.

Let’s see how the behavior of point 1 and point 3 changes, making this small change to our code:

...
Object.defineProperty(student, 'birthday', {
writable: true,
enumerable: true,
value: '4th July'
})
...

Having set the writable and enumerable properties to true, the script will not exit with an error and will also display the birthday property that previously was not visible:

{ name: 'Davide', number: '123456', birthday: '4th July' }
4th July
Another date

In JavaScript, you can delete the properties of an object using delete. Now let’s try to delete the birthday property defined previously. Let’s add these two lines at the end of our script:

...
Object.defineProperty(student, 'birthday', {
writable: true,
enumerable: true,
value: '4th July'
})
...
delete student.birthday
console.log(student)

If we run this script we will get the following error at run time:

TypeError: Cannot delete property 'birthday' of #<Object>

We get this error as the configurable parameter default value its false. This means that it is not possible to delete the birthday property from our object. If we try to set it to true as follows:

...
Object.defineProperty(student, 'birthday', {
configurable: true,
writable: true,
enumerable: true,
value: '4th July'
})
...
delete student.birthday
console.log(student)

The script will no longer end with an error, but as expected the results will be correctly printed on the screen:

{ name: 'Davide', number: '123456', birthday: '4th July' }
4th July
Another date
{ name: 'Davide', number: '123456' }

Prototype

All JavaScript objects have [[prototype]]. This property is an implicit reference to another object that is queried in property searches.

If an object does not have a particular property, the prototype of the object is checked for that property. If the object prototype does not have that property, the object prototype is checked and so on. This is how inheritance works in JavaScript, JavaScript is a prototype language. This will be explored in detail later in this article.

Object.create

In JavaScript this method can be used to replace new keyword. We can use it to create an empty object based on a defined prototype and then assign it to a different prototype:

const anotherObject = {
a: 2
}

// create an object linked to anotherObject
const myObject = Object.create(anotherObject)

myObject.a // the value will be 2

Now myObject is linked to anotherObject. Clearly myObject.a does not actually exist but, however, access to the property is successful because exists on anotherObject and in fact finds the value 2.

Inheritance

Basically in JavaScript the inheritance it’s obtained with a chain of [[prototype]]. There are several ways to create this prototype chain, but in this article we will analyze the most common:

  • Functional
  • Constructor functions
  • Classes

Using [[prototype]] we can add new properties and methods to an existing object constructor. We can then essentially tell our JavaScript code to inherit properties from a prototype. This prototype chain allows us to reuse properties and methods from one JavaScript object to another via a pointer function reference. Wanting to make a comparison with classical inheritance, here’s what happens graphically:

Functional inheritance

To create the prototype chains with this methodology just use the Object.create function showed previously. Let’s see how to do it with an example:

const mammal = {
introduceYourself: function () {
console.log(`Hello I'm a ${this.type} and my name is: ${this.name}`)
}
}
const cat = Object.create(mammal, {
type: { value: 'cat' },
noise: { value: 'meow' },
meow: { value: function () {
console.log(`I ${this.verso}: MEEEEEOOOOOW`)
}}
})
const dog = Object.create(mammal, {
type: { value: 'dog' },
noise: { value: 'bark' },
bark: { value: function () {
console.log(`I ${this.noise}: WOOF WOOF`)
}}
})
const fuffy = Object.create(cat, { name: { value: 'Fuffy' } })
fuffy.introduceYourself()
fuffy.meow()
const bobby = Object.create(dog, { name: { value: 'Bobby' } })
bobby.introduceYourself()
bobby.bark()

The mammal object is a simple JavaScript object, defined using the braces {}. The prototype for simple objects like this is Object.prototype. The Object.create function, described previously, we have created the cat and dog objects passing the prototype of the desired object as first argument, in this case mammal. So mammal is the prototype of cat and dog. When the bobby and fuffy objects are created, they are passed as first argument to the Object.create function, dog and cat, respectively. So dog is the prototype of bobby and cat is the prototype of fuffy. The entire prototype chain is:

  • The prototype of fuffy is cat;
  • The prototype of bobby is dog;
  • The dog and cat prototype is mammal;
  • The mammal prototype is Object.prototype.

Analyzing the steps performed by fuffy.introduceYourself() (and in the same way bobby.introduceYourself()), let’s try to understand even better which steps are performed:

  • It is checked if fuffy has a property introduceYourself; It is not so;
  • It is checked whether the prototype of fuffy, cat, has a property introduceYourself; It is not so;
  • It is checked whether the prototype of cat, mammal, has a property introduceYourself; performs it;
  • Performs the function introduceYourself on fuffy, so mammal type, this.type will be cat and this.name will be “Fuffy”.

To complete the functional paradigm applied to prototype inheritance, the creation of an instance of a dog and a cat can be generalized with a function:

...

function createCat (name) {
return Object.create(cat, {
name: { value: name }
})
}
function createDog (name) {
return Object.create(dog, {
name: { value: name }
})
}
const fuffy = createCat('Fuffy')
fuffy.introduceYourself()
fuffy.meow()
const bobby = createCat('Bobby')
bobby.introduceYourself()
bobby.bark()

Remember that the prototype of an object can be inspected with Object.getPrototypeOf():

console.log(Object.getPrototypeOf(fuffy) === cat) //true
console.log(Object.getPrototypeOf(bobby) === dog) //true

Constructor functions Inheritance

This approach is frequently used and simple, just declare a function and call it using the new keyword. Let’s go back to the example showed before:

function Mammal (name) {
this.name = name
}
Mammal.prototype.introduceYourself = function () {
console.log(`Hello I'm a ${this.type} and my name is: ${this.name}`)
}
function Cat (name) {
this.type = 'cat'
this.noise = 'meow'
Mammal.call(this, name)
}
Cat.prototype.meow = function () {
console.log(`I ${this.noise}: MEEEEEOOOOOW`)
}
Object.setPrototypeOf(Cat.prototype, Mammal.prototype)function Dog (name) {
this.type = 'dog'
this.noise = 'bark'

Mammal.call(this, name)
}
Dog.prototype.bark = function () {
console.log(`I ${this.noise}: WOOF WOOF`)
}
Object.setPrototypeOf(Dog.prototype, Mammal.prototype)const fuffy = new Cat('Fuffy')
fuffy.introduceYourself()
fuffy.meow()
const bobby = new Dog('Bobby')
bobby.introduceYourself()
bobby.bark()

The constructor functions Dog, Cat, and Mammal have a first capital letter. Just as in Object Oriented Programming in general it is a convention, also in this case it is.

When new Cat('Fuffy') is invoked, a new instance of Cat is created (fuffy). This new object is also the this object inside the Cat constructor function. After,Cat passes the reference to itself, this, to Mammal.call. Using this method allows you to set the this object of the function called through the first argument that was passed to it. Then, when this is passed to Mammal.call, the reference is also created to the newly created object (which is eventually assigned to fuffy) via the this object inside the Cat constructor function. All subsequent arguments passed to the call become the arguments of the function, so the argument of the name passed to Mammal is “Fuffy”. The Mammal constructor sets this.name to “Fuffy”, which means that eventually fuffy.name will also be “Fuffy”. So if we want to describe the entire prototype chain:

  • The fuffy prototype is Cat.prototype;
  • The bobby prototype is Dog.prototype;
  • The dog and cat prototype is Mammal.prototype;
  • The Mammal prototype is Object.prototype.

Classes inheritance

A little syntactic sugar has been introduced in modern versions of JavaScript. Obviously we are talking about the keyword class, and it is recommended not to confuse this keyword with the same word in other OOP languages, such as Java.

This keyword in JavaScript does nothing more than create a constructor function which must then be called with new. In fact, if you use the developer console of any browser and try to type :

typeof class Mammal {}

the results will be:

The use of class only reduces the number of lines of code, thereby increasing readability, which is useful for creating prototype chains. Let us now return to our example for the third and last time:

class Mammal {
constructor (name) {
this.name = name
}
introduceYourself () {
console.log(`Hello I'm a ${this.type} and my name is: ${this.name}`)
}
}
class Cat extends Mammal {
constructor (name) {
super(name)
this.type = 'cat'
this.noise = 'meow'
}
meow () {
console.log(`I ${this.noise}: MEEEEEOOOOOW`)
}
}
class Dog extends Mammal {
constructor (name) {
super(name)
this.type = 'dog'
this.noise = 'bark'
}
bark () {
console.log(`I ${this.noise}: WOOF WOOF`)
}
}
const fuffy = new Cat('Fuffy')
fuffy.introduceYourself()
fuffy.meow()
const bobby = new Dog('Bobby')
bobby.introduceYourself()
bobby.bark()

Also in this case the prototype chain is:

  • The fuffy prototype is Cat.prototype;
  • The bobby prototype is Dog.prototype;
  • The dog and cat prototype is Mammal.prototype;
  • The Mammal prototype is Object.prototype.

The extends keyword makes prototype inheritance much easier. In the example code, the Dog extends Mammal class will ensure that the prototype of Dog.prototype will be Mammal.prototype. The constructor method in each class is the equivalent of the function body of a constructor function. So, for example, the function:

function Dog (name) {
this.name = name
}

is the equivalent of:

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

The super keyword in the Dog class constructor method is a generic way to call the parent class constructor by setting this into the current instance. In the example of the constructor function Dog.call(this, name) here becomes the equivalent of super(name).

Conclusion

The concept of prototype inheritance in JavaScript often causes some confusion. I hope that by reading this article it will be clearer and much simpler. Well for now …

HAPPY, CODING!

Bibliography

More content at plainenglish.io

--

--

👨‍🎓Degree in computer science 💑 Married with Milena 🤓 Huge Nerd! 💻 Code lover 👨🏻‍💻 Fullstack developer