To most early stage developers, especially the ones who come from a more strongly-typed background in coding, some foundational JS concepts can seem confusing at times, especially the more traditionally borrowed ones like this, class
, Prototypes
etc. I have dug deeper into the this
keyword and some useful tips to eyeball it in my last post of this series.
Today, lets build on that and understand Prototypes
and class
a little better .
What is this
and a this
-aware function?
A function's this
references the execution context
for that call, determined entirely by how the function was called.
A this
-aware function can thus, have a different context
each time it's called, which makes it more flexible & reusable.
Consider the following two examples :
var school = {
teacher: "John",
ask(question) {
console.log(this.teacher, question);
}
}
school.ask("what do you teach?");
// John what do you teach?
function ask(question) {
console.log(this.teacher, question);
}
function someOtherClass() {
var egContext = {
teacher: "Peter",
};
ask.call(egContext, "how long have you been working here?");
}
someOtherClass();
The first example above shows how we can implicitly
bind a this
context and the latter shows an example of an explicitly
bound one.
Prototype.. huh..wha?
It's nothing too fancy, I promise you. Let's build on an example together.
Say we wanted to create something similar to a class
that we could instantiate later as required. We could do that by defining a function like School
above, and making it this
-aware. This function is going to act like a constructor
for the instances of this so-called School
class.
function School(teacher) {
this.teacher = teacher;
}
Now, if we wanted to add methods to School
, we could simply chain them to the .prototype
of the School
constructor, as we have done with the ask()
method below.
School.prototype.ask = function(question) {
console.log(this.teacher, question);
}
Note: If you did School.ask()
instead of School.prototype.ask()
, that would only chain .ask()
to the School
itself and not to any other instances of the so-called School
class, which defeats our purpose of trying to build an encapsulating entity that could be instantiated later. Here's an example to demo this:
School.ask = function(question) {
console.log(this.teacher, question);
}
var inst = new School("John");
// School -> function(teacher) {
// this.teacher = teacher;
// }
//
// School.ask -> function(question) {
// console.log(this.teacher, question);
// }
//
// inst.ask -> undefined
// inst.ask("Why?") -> Uncaught TypeError: inst.ask is not a function
Now, with that out of the way, let's continue building further and create two instances of School
.
var exOne = new School("John");
var exTwo = new School("Peter");
exOne.ask("what do you teach?");
// John what do you teach?
exTwo.ask("how long have you been working here?");
// Peter how long have you been working here?
In a way, you could look at the prototype
as an object where any instances are going to be linked or delegated to. As an example, when we say exOne = new School("John")
, the new
keyword is going to invoke the School
function we defined above, and the object that gets created is going to be linked to School.prototype
.
And since School.prototype
has an ask()
method, we can take the exOne
instance and call ask()
on it.
It is important to note that exOne
as an object does not have an ask()
method, but the code above works just fine, and we say the instance exOne
is prototype linked
to School.prototype
. And since, we also made ask()
a this
-aware function, we see the output to be John what do you teach?
This, in a very brief nutshell, is prototypal class pattern
.
The class
keyword
So now that we are armed with our understanding of this
keyword and the prototype system
, let's dig deeper into what happens under-the-hood when we use class
keyword in JS.
class
is layered on top of the prototype system
. It basically gives us a less awkward and a more cleaner implementation of the prototypal style
.
Let's tweak the aforementioned example, to see the class
implementation:
class School {
constructor(teacher) {
this.teacher = teacher;
}
ask(question) {
console.log(this.teacher, question);
}
}
var exOne = new School("John");
exOne.ask("what do you teach?");
// John what do you teach?
So with this type of implementation, we use the class
keyword and then inside the body of the function, we have a constructor()
which contains everything from the function School()
above. To add other methods like ask()
, we put them directly inside the class
, with their respective definitions. This syntactical sugar makes the code much cleaner and improves readability.
It's important to note here that under the covers, JavaScript is doing the exact same thing as what we saw earlier with the prototypal
implementation.
Conclusion:
To be completely honest, the prototypal
implementation is increasingly becoming rare to spot in people's codes, but I still believe, understanding the basic concepts and how Javascript works behind the curtains, can really help build a stronger foundation and give you the confidence to build great products. Hope you could take something from it. Happy coding!