Borislav Hadzhiev
Wed Feb 24 2021·3 min read
Photo by Arun Clarke
ES2015 classes in JavaScript are just syntactic sugar, they don't really exist.
Classes were added in to make working with inheritance easier for programmers who were used to working with classes with the traditional OOP approach.
When we use an ES2015 class, it just uses the prototype system behind the scenes - even though we're using the class syntax we're still making use of prototypal inheritance.
Let's say we have the following class:
class Employee { constructor(title) { this.title = title; } work() { console.log(`The ${this.title} is working.`); } }
The real code that gets ran would be the following constructor function:
function Employee(title) { this.title = title; } Employee.prototype.work = function () { console.log(`The ${this.title} is working.`); };
We could then use this constructor function to initialize/use the different properties / methods we want an Employee to have:
const employee = new Employee('programmer'); employee.work();
Inside of the constructor function we generally only associate the properties that we want an object to have, because the properties are mostly going to be unique between the different instances we create.
When it comes to methods - we place them on the prototype
object - because the
methods are most commonly the same between the instances, and it would be a
waste of resources to duplicate those methods on every instance we create.
The solution used in the javascript language is to delegate the methods to the
prototype
object.
If you were to console.log
the employee
instance we created above you would
see something like:
{ title: 'programmer', __proto__: { work: () => {}, ... } }
So we don't see the prototype
property on the instance but we see a
__proto__
property that has access to the work
method.
So how is __proto__
related to prototype
?
__proto__
is the actual object that is used in the lookup chain to resolve
methods. When we try to access a method like work
directly on the instance
employee
- javascript first looks for the work
method directly on the
instance, when it doesn't find it - it works for the work
method on the
__proto__
object.
prototype
is the object that is used to build __proto__
when we create an
object with the new
keyword. We assigned the work
method on the
prototype
object, which is then used to build __proto__
which is used by
the instances to look for methods, when they aren't found directly on the
instance.
Another benefit of this delegation approach is that we can attach methods to the
prototype
object of the class after creating an instance, and still use that
method on the instance - because javascript first is going to look for the
method on the instance and when it doesn't find it - it will look on the
prototype.
function Employee(title) { this.title = title; } Employee.prototype.work = function () { console.log(`The ${this.title} is working.`); }; const employee = new Employee('programmer'); Employee.prototype.drinkCoffee = function () { console.log(`The ${this.title} drinks coffee`); }; // works employee.drinkCoffee();
We can look back at a prototype and add, update or remove methods, and then they will be reflected between all the instances created from that constructor function.
When we use classes in javascript - we still use the prototype
system, every
class translates to a constructor function with a prototype that stores all the
different methods associated with the class.