I'll give you the low-down on classes in JavaScript and classes in traditional OOP languages, say Python.
Traditional OOP languages
In traditional OOP languages, a class is a blueprint for objects. In terms of information management, the class is used to provide a) abstraction (which information is relevant? which is irrelevant?) and b) encapsulation (how do I hide/show what is relevant/irrelevant?). Through a class, I can create multiple implementations of this blueprint. These implementations are called instances. Each instance gets a copy of the methods and instance variables of the class.
classPerson:def__init__(self,first_name,last_name):self.first_name=first_nameself.last_name=last_namedefprint_full_name(self):print(f'{self.first_name}{self.last_name}')# Adrian Perea
person_a=Person('Adrian','Perea')person_b=Person('G','Nio')person_a.print_full_name()# Adrian Perea
person_b.print_full_name()# G Nio
In this example, person_a and person_b are instances of the blueprint (read: class) Person. Each of them gets its own first_name and last_name instance variables, and print_full_name method.
JavaScript
Now JavaScript, while seemingly does the same thing, does this differently. Whenever we use the new keyword to execute a function, we create an implicit object that we can reference with this inside the function. Furthermore, objects created with a constructor function have a reference to the constructor function inside their own prototypes. That was a lot. Let's try to unpack this with code.
Let's first make an object without any fancy constructor functions:
functionPerson(first_name,last_name){return{first_name,last_name,fullName(){console.log(`${this.first_name}${this.last_name}`)}}}constperson_a=Person('Adrian','Perea')constperson_b=Person('G','Nio')peron_a.fullName()// Adrian Pereaperson_b.fullName()// G Nio
This works fully well. Why not call it a day and be done with it?
The brutally honest truth is, we can call it a day and be done with it. There are a lot of use cases that you can accomplish by just creating objects this way. But just because we can, doesn't mean we should, especially if there are arguably better ways of accomplishing the same thing. If we proceed with this route, we're not taking advantage of the prototypical inheritance that makes JavaScript unique (not necessarily better nor worse).
Let's take a look at another way we can implement this:
functionPerson(first_name,last_name){this.first_name=first_name;this.last_name=last_name;}Person.prototype.fullName=function(){console.log(`${first_name}${last_name}`);}constperson_a=newPerson('Adrian','Perea');constperson_b=newPerson('G','Nio');person_a.fullName()// Adrian Pereaperson_b.fullName()// G Nio
As I mentioned earlier, there are two things that are happening whenever we use the new keyword.
An implicit object is created and is referred to as this. So, when we do this.firstName or this.lastName, it's as if we're assigning properties to an empty object.
Objects created with a constructor function have a reference to the constructor function inside their own prototypes. After creating the constructor function above and its instances, try to type the following in your browser console:
person_a.__proto__===Person.prototype// true
Whenever we create a new instance of the Person constructor function through the new keyword, a reference to the prototype object of the constructor function gets added to the __proto__ property of the object. Read that again. After that, read it one more time. This bit is important.
As opposed to traditional OOP languages, methods are not copied to each instance of the class. When we call person_a.fullName(), JavaScript climbs up the __proto__ properties of the instances until it finds a qualified name of fullName(). Therefore, the method fullName() lives completely inside the prototype of the constructor function. This provides performance benefits since the methods only have to be defined once (on the prototype). That is, person_a.fullName === person_b.fullName === Person.prototype.fullName.
This means that whatever we define on Person.prototype will be available to all instances of Person. So we can do something weird (in traditional OOP sense) like this:
Person.prototype.lastName=function(){console.log(this.lastName);}// Note that we did not recreate the objects here!person_a.lastName()// Pereaperson_b.lastName()// Nio
I hope that this clears everything up. To summarize:
constructor functions do two things: create an implicit object that is referenced by this, and assign the __proto__ property of each instance as a reference to the prototype object of the constructor function
When a function is called on the instance, the prototype chain is climbed until a reference to the function is found
In traditional OOP, all instances have a copy of each method. There is no concept of prototype
I didn't include a bit about inheritance. But I think that this information is enough to get you to understand it on your own.
A note about ES6 "classes"
Syntactic sugar. It's quite cumbersome to write prototype for each method you want to share amongst instances (in addition to breaking encapsulation at the textual level). ES6 class syntax is just an easy way to store everything in one place, without having to mess with prototype and all of that jazz.
Have had many hats on in my life: Developer, Team Lead, Scrum Master, Architect and Product Owner. Now back to developer \o/ Interested in product discovery, quality assurance and language design.
Adrian, nice! 👍 I once read an entire book in order to learn what you just covered in this comment.
Feel free to paste this comment into a whole new post, since it sure is worth it's own post on DEV!
Thank you so much for taking the time to write this reply. As Daniel mentioned, this deserve its own post. I'm sorry I didn't reply before, but I live among persistent distractions and internet issues. Thank you so much. I'm reviewing it now!
I'll give you the low-down on
classes
in JavaScript andclasses
in traditional OOP languages, say Python.Traditional OOP languages
In traditional OOP languages, a
class
is a blueprint for objects. In terms of information management, the class is used to provide a) abstraction (which information is relevant? which is irrelevant?) and b) encapsulation (how do I hide/show what is relevant/irrelevant?). Through a class, I can create multiple implementations of this blueprint. These implementations are calledinstances
. Each instance gets a copy of themethods
andinstance variables
of the class.In this example,
person_a
andperson_b
areinstances
of the blueprint (read: class)Person
. Each of them gets its ownfirst_name
andlast_name
instance variables, andprint_full_name
method.JavaScript
Now JavaScript, while seemingly does the same thing, does this differently. Whenever we use the
new
keyword to execute a function, we create an implicit object that we can reference withthis
inside the function. Furthermore, objects created with a constructor function have a reference to the constructor function inside their own prototypes. That was a lot. Let's try to unpack this with code.Let's first make an object without any fancy constructor functions:
This works fully well. Why not call it a day and be done with it?
The brutally honest truth is, we can call it a day and be done with it. There are a lot of use cases that you can accomplish by just creating objects this way. But just because we can, doesn't mean we should, especially if there are arguably better ways of accomplishing the same thing. If we proceed with this route, we're not taking advantage of the prototypical inheritance that makes JavaScript unique (not necessarily better nor worse).
Let's take a look at another way we can implement this:
As I mentioned earlier, there are two things that are happening whenever we use the
new
keyword.An implicit object is created and is referred to as
this
. So, when we dothis.firstName
orthis.lastName
, it's as if we're assigning properties to an empty object.Objects created with a constructor function have a reference to the constructor function inside their own prototypes. After creating the constructor function above and its instances, try to type the following in your browser console:
Whenever we create a new
instance
of thePerson
constructor function through thenew
keyword, a reference to theprototype
object of theconstructor function
gets added to the__proto__
property of the object. Read that again. After that, read it one more time. This bit is important.As opposed to traditional OOP languages, methods are not copied to each
instance
of the class. When we callperson_a.fullName()
, JavaScript climbs up the__proto__
properties of the instances until it finds a qualified name offullName()
. Therefore, the methodfullName()
lives completely inside theprototype
of theconstructor function
. This provides performance benefits since the methods only have to be defined once (on the prototype). That is,person_a.fullName === person_b.fullName === Person.prototype.fullName
.This means that whatever we define on
Person.prototype
will be available to all instances ofPerson
. So we can do something weird (in traditional OOP sense) like this:I hope that this clears everything up. To summarize:
this
, and assign the__proto__
property of each instance as a reference to theprototype
object of theconstructor function
prototype chain
is climbed until a reference to the function is foundI didn't include a bit about inheritance. But I think that this information is enough to get you to understand it on your own.
A note about ES6 "classes"
Syntactic sugar. It's quite cumbersome to write
prototype
for each method you want to share amongst instances (in addition to breaking encapsulation at the textual level). ES6 class syntax is just an easy way to store everything in one place, without having to mess withprototype
and all of that jazz.I hope this was helpful.
Cheers,
Adrian
Adrian, nice! 👍 I once read an entire book in order to learn what you just covered in this comment.
Feel free to paste this comment into a whole new post, since it sure is worth it's own post on DEV!
Wow! Thanks for the nice comment, Daniel! Working on its own post right now. Look out for it!
Thank you so much for taking the time to write this reply. As Daniel mentioned, this deserve its own post. I'm sorry I didn't reply before, but I live among persistent distractions and internet issues. Thank you so much. I'm reviewing it now!
Years later this response have helped me so much. Thank you so much Adrian.