A deeper look at object-oriented programming in JavaScript

Vidhyanshu Jain
Level Up Coding
Published in
8 min readJun 2, 2020

--

Photo by Greg Rakozy on Unsplash

JavaScript is a class-less programming language. Classes, as you know in other languages like Java, do not exist in JavaScript.

Even though JavaScript doesn’t provide support for Classes, clever use of existing concepts (constructor function and prototypal inheritance) in JavaScript allowed the developers to emulate this behavior successfully. So the class keyword which you can find in JavaScript after the ES6 update is a syntactical abstraction for the steps required to emulate classes using existing JavaScript concepts. In this post, we will see what are the underlying concepts behind the class keyword.

But first, let’s see two of the most simple ways to create objects

Object Constructor and Object literals

JavaScript lets you create objects using the Object() constructor. The Object constructor creates an object wrapper for the given value. And if the value is null it will create and returns an empty object.

In the above example by calling the Object constructor with empty parameters we have initialized an empty object and assigned it to the variable dog. Then we simply add object properties with the help of the dog variable

After the initial years of JavaScript, object literals became the preferred way of creating objects. We can rewrite our previous example as:

Though very simple to use Object Constructor and Object literal have some downsides when creating objects. Creating multiple objects with the same interface requires a lot of code duplication.

Creating Objects

We could just simply use class to create objects and that is the preferred way since the introduction of classes in ES6. But to understand how OOP in Javascript works, we must understand the underlying concepts that are replaced by classes. So we should take a look at some ways to implement Object creation with interfaces.

The function constructor pattern

JavaScript objects can be created using a constructor. There are some native constructors available right out the box in JavaScript, such as Object and Array. It is possible to define custom constructors using functions that define the properties and methods. So let's start by creating an object called dog.

Note that to create an object using the dog function as a constructor we use the new operator in line number 10. When line 10 is executed the following steps take place:

  1. A new Object is created in the memory
  2. It sets this new object’s internal, inaccessible, [[prototype]] property to be the constructor function’s external, accessible, prototype object (every function object automatically has a prototype property).
  3. The this value is assigned to the newly created object.
  4. It executes the constructor function, using the newly created object whenever this is mentioned.
  5. the newly created object is returned unless the constructor function returns a non-null object reference. In this case, that object reference is returned instead.

The only difference between constructor functions and other functions is the way in which they are called. Any function that is called with the new operator acts as a constructor.

While creating the object using the constructor, one thing worth noticing is that tuffy object had his own copy of the properties present inside the dog constructor. ie. changing the property associated with one object will have no effect in the properties of the constructor function. Let’s take a look at an example:

As you can see from the above example changing the toys property in the tuffy object has not affect on the toys property inside the tyson object. Because both tuffy and tyson have their own copies of constructor properties.

But due to this nature of objects having their own copy of properties we can face memory related issue. As multiple copies of the same property takes up a lot of space. This is the major drawback of the function constructed pattern.

The Prototype pattern

Whenever you create a function in JavaScript you will notice that an object name prototype is appended in the definition of the function. Inside the prototype object we can add properties that the resulting object should inherit. The benefit of using a prototype is that the resulting object will not have a copy of the properties but it can still access the properties and methods by searching for them in the prototype object.

Let’s take an example:

The way prototype works is that when a property is accessed a search starts to find the property. The search begins on the object first and if the property is found in the object then it returns the value. Like when we tried to call dog.legs in line 12 it returns the value 4 as the property legs exist in the object. And when the property is not found in the object the search continues up the prototype. And the prototype is searched for the property. If the property is found on the prototype then it returns the value. If not found the search continues up the prototype chain till it encounters the property. Like when we tried to access the property legs on boy the search moves to the prototype because the object boy doesn’t have the property leg. Since the prototype of the object boy has the property legs it returns the value.

One thing that we need to keep in mind that prototypes are dynamic. So any changes made in the prototype at any point will be reflected on the objects.

Even the prototype pattern is not perfect. the first downside is that you don’t have the ability to pass the arguments like we could do it in function constructors. But honestly, this is not a very big downside it’s inconvenient but developers can easily overcome it.

But the real problem arrives with reference properties. Let's take an example to understand this drawback:

As you can see that dog1 and dog2 both have the same property. This happened because in line 6 the push function is actually referencing the property which returns the toys property present inside the prototype and added a new value to the toys array. So properties that can be referenced can cause this inconsistent behavior.

This behavior of prototypes can be desirable for some and could be a problem for others. Although generally it is desired for the objects to have their own copies of properties. Hence it is rare that prototype pattern is used alone.

Inheritance

Inheritance in JavaScript is primarily done through prototype chaining. First, we should know what prototype chaining is.

Prototype chaining

JavaScript has only one construct that is the object. As we have seen that each object has a property called prototype. A prototype is an object which points to the properties and methods of the constructor. But it's not necessary for the prototype to be constructor, it can be an object also. And that object will also have a prototype of its own and its prototype might have a prototype of its own and this pattern will continue, forming a chain of prototypes called prototype chain.

Let’s take a look at how prototype chaining is implemented:

So as you can see that human inherits animals by pointing its prototype pointer to the animals object. So when we access Jon.canWalk it searches for the canWalk property in its object then inside its prototype: human object and then the search reaches to animals object and finally returning its value.

The problem with prototype chaining is the same problem that we face with prototypes. When we are using properties that contains reference values these properties are shared among the objects and this is why properties are defined using constructors.

Properties are typically defined in constructor instead of using prototypes. Because we have seen that properties with reference values can be irritating.

Classical Inheritance

Classical Inheritance or Constructor Stealing or Masquerading all means the same thing. The idea behind classical inheritance is to call the constructor of an object inside the constructor of another object. We can leverage the apply() and call() methods to execute the constructor on a newly created object. Let’s take a look at the example.

By using the call() or apply() method we are essentially stealing the constructor function from the shapes. The shapes constructor when called inside the square constructor it is running the code initialization of the shapes constructor and because of which every object of square type has a copy of its own colors.

Constructor stealing (classical inheritance) comes with a benefit over prototype chaining. With classical inheritance we have the ability to pass arguments which was a downside in prototype chaining.

That being said classical inheritance has its own drawback. Methods must be written inside the constructor so there’s no function reuse. Constructor stealing doesn’t automatically create prototypes hence a sub-type object can’t use the prototype from the super-type.

Combined Inheritance

Combined Inheritance (also known as pseudo-classical inheritance) is a combination of prototype chaining and classical inheritance. So we can inherit properties and methods from the prototype and use classical inheritance to inherit instance properties.

As you can see that we have used constructor stealing(classical inheritance) to inherit properties and we have used prototype chaining to inherit methods. So we have created two instance of object dog which have their own copies of properties and at the same time they are sharing a method.

Classes

Up until this point we have gone through the in-depth view of how it is possible to emulate classes in JavaScript. But implementing these methods can be tedious task and it increases the probability of creating bugs in your code. So starting from ES6 class was introduced that abstracts the logic behind OOPs in JavaScript. The ES6 classes appear to feature canonical object-oriented- programming, but they still use the prototypes and constructor under the hood.

We can write a class definition in two ways: class declaration and class expressions

Note: By default, everything inside the class method executes in strict mode.

A class can be composed of the constructor, get methods, set methods, methods, and static methods.

To create an object using class we use the new keyword. And the new keyword will follow the same steps that we saw when we created an object using function constructor in our previous sections.

In JavaScript, classes are first-class citizens. That means that classes can do everything that other things can do. Classes can be passed around like you would pass any other object or function reference. Classes can be used anywhere a function (for example in arrays and function parameters).

Below are some of the members that we generally see inside a class object:

Inside a class, all the members and properties defined inside the constructor will not be shared to prototype. Every Instance will have a copy of all the properties and methods inside the constructor. These properties are the same as we saw when we used function constructors while creating objects.

And everything defined outside the constructor and in the root of the class body is appended to the prototype object.

Note: Primitive data types and objects can not be added to the root of the class.

Static methods are methods that can be used even without an instance of the class. As we can see in Line 16 the method sayHello was used without creating an instance of the class because it was a static method.

Inheritance

One more addition of the ES6 update was inheritance. The syntax is different but under the hood class inheritance still uses prototype chaining to achieve inheritance.

The extend keyword allows to inherit from anything that has a [[construct]] property and a prototype. It means it is backward compatible so, you can inherit from another class and you can also inherit from function constructors.

In this example, we are inheriting the properties and method in the Dog class from the Animal class. We have used the super() method to invoke the constructor of the Animal class.

This concludes OOPs pattern in JavaScript. We have seen that without having any formal concept of class we can still achieve classes and inheritance in JS. It’s important that we know how exactly JavaScript works. Prototypes and function constructors are important in JavaScript and one should invest time to learn about these.

Happy Coding :)

--

--