ES6 — Let’s divide our phones into Classes
JavaScript is strictly a prototype-based language and treats everything under one form: Object.
OK. So what why do we need Classes in ES6? Are they similar to the Classes in other language like Java, C#, etc? Does it change JavaScript’s core from prototype-based to class-based?
Well, let us find out.
First of all, what is “class-based” and “prototype-based” language?
Class-based VS Prototype-based
Class-based language
- Objects are built based on
class
, which acts as blueprint/template — definition and description of an object. - All objects are created on run-time using syntax
new
as instances of certain classes, and classes need to be pre-defined explicitly. - Inheritance here is performed between classes, aka class inherits directly from another class(es).
Prototype-based language (JavaScript)
- In JavaScript, there is only one thing matters: objects — which means:
“What matters about an object is what it can do, not what it is descended from.”
Hence JavaScript doesn’t do ‘casting’, aka there is no template as ‘class’ and object is not instantiate from a class. Class is not needed, and objects can inherit directly from other objects.
- In addition, each object automatically has a private/hidden property
[[Prototype]]
— reference tonull
or to another object instance which is called a prototype. On newly created object, its prototype is initialized as object which is{ constructor: this }
- JavaScript uses “differential inheritance” model, which is performed with the help of prototype chain — child object will not need to duplicate all properties and methods of its parent, instead it will have “hidden link” referred back to its parent object as a prototype. For example:
Any access request to inherited properties will be delegated to its parent’s properties respectively, which means Object.hasOwnProperty()
for inherited properties on the child object will return false
. In the example above, we can call dog.beCute()
but the property beCute
doesn’t not exist in dog
, only in its prototype — aka its parent.
Note: __proto__
is an accessor property (a getter function and a setter function) that exposes the internal [[Prototype]]
. It is strongly not recommended to directly access/modify this.
Got the general idea? Great. Now let’s understand how we “define” and “implement” class-like object types in JavaScript before and after ES6.
Before ES6 with Function
Declaring an object type
In general, “mimic”-ing traditional Object Oriented class-based coding style like in Java, C#, etc… can be achieved in JavaScript by taking advantage of “functional object”.
For instance, below is how you declare an object type named CellPhone
that can be initialized with basic functionalities: charge, call and sms.
As you can see, all methods are declared in CellPhone.prototype
but not to CellPhone
itself to make sure that any object instance created/inherited from CellPhone
will have direct access to these methods (without using the word prototype
) .
Any method declared directly to CellPhone
will be considered as “static” method, which means it exists as local property of CellPhone
only and won’t be available to its instances.
For example, if we add static method showTime
directly to CellPhone
Then
var myPhone = new CellPhone();myPhone.charge();
myPhone.call({name: 'Maya', phoneNum: 12345}); //"Calling Maya"
myPhone.showTime(); //TypeError: myPhone.showTime is not a functionCellPhone.charge(); //TypeError: CellPhone.charge is not a function
CellPhone.showTime(); //Display the correct time.
Inheritance
As i mentioned earlier, JavaScript is prototype-based language and each prototype is actually an independent object, hence in order to create proper inheritance, we will need the help of Object.create()
.
Explanation: Object.create()
is meant to create new object with specified prototype and properties.
And therefore:
let iPhone = new SmartPhone('iPhone');
iPhone.charge();
console.log(iPhone.battery); //1
iPhone.call({name: 'Pikachu', phone: 007}); //Calling Pikachu
Note: However, this coding style is definitely NOT recommended, for various reasons, one of them is performance — CellPhone
is actually an object itself and created during run-time, unlike in other languages — another is code readability. We will discuss more about this in different article 😄.
So how does the chaining look like? Here is a glimpse of objects created in our example:
Since we know this coding style is not idealistic, what’s about Classes? Can they help us write better code? Let’s see…
After ES6 with Classes
What are Classes?
First, in JavaScript, Classes are no different from Functions. They are ‘special’ functions, by definition. Hence:
class CellPhone{}console.log(typeof CellPhone === "function"); //true
As a result, Classes have similar components like Functions, which are:
Classes declaration
Back to our example — CellPhone
, syntax for declaring object type now is similar but simpler than before:
//class <Name of Object> {}
class CellPhone{}
And CellPhone
can be declared in the following form:
As shown, there are several small improvements here:
- No need for comma keyword
,
in-between properties or methods like in Object declaration syntax. - More organized code, with a proper
constructor
to indicate howCellPhone
should be created and initialized. - No more
CellPhone.prototype.
syntax to define its prototype properties and methods. Less typing, less typo mistakes 😄! - Direct properties of class can only be created/initialized inside
constructor
. Constructor acts as the class definition and handles all the dirty work of setting up the prototype chain properly.
class CellPhone{
this.battery = 0; //SyntaxError
}function CellPhone(){
this.battery = 0; //OK
}
What about static methods?
Good news, ES6 actually provides (finally) the keyword static
to enhance readability (thank God!) for us developers. Thereby we can define static methods directly in class
declaration just like other prototype methods following the syntax:
Clearer, isn’t it? Now you don’t need to try to remember which method is static, and which is not. It’s finally getting simpler 💃!
Classes expression
Similar to Function expression, class expression can be named or unnamed. And the name given stays local to the class’s body, which means:
let cellPhone = class{
constructor(){}
},
cellPhone2 = class randomPhone{
constructor(){ this.battery = 0; }
};console.log(cellPhone.name); //cellPhone
console.log(cellPhone2.name); //randomPhone
In case class name is not given, the callprototype.name
will retrieve the name of the instance/variable assigned to that unnamed class. Otherwise, it will always take given class name.
So far so good? Awesome.
Next, how do Classes handle inheritance, will Classes help us write better code?
Inheritance
“Extends” keyword
Familiar with Java? If so, you probably are familiar with the keyword extends
used for inheritance there. Similarly, ES6 brings us extends
as more proper and readable way to implement inheritance, instead of using Object.create()
How do we use it? By the syntax:
class <sub-class name> extends <class name>{}
In our example, to create sub-class SmartPhone
inherits from CellPhone
will be done as followed:
class SmartPhone extends CellPhone{}
How about accessing parent class? For that we have
Super-class
In addition, ES6 introduces another keyword super
which referred to parent class (super-class) and is used to call corresponding methods of parent class inside subclass declaration.
By definition:
The prototype of a subclass is the super-class.
Important note:
this
is originally uninitialized in subclass constructor. Therefore, ifconstructor
presents in subclass, before accessing any local property for that sub-class viathis
, constructor of super-classsuper()
MUST be called to initializethis
.super()
can only be called ONCE,ReferenceError
will be thrown otherwise.- If the constructor explicitly returns an object, it will be used as the result instance, and it doesn’t matter whether
this
is initialized anymore.
Hence the following code is OK:
class SmartPhone extends CellPhone{
constructor(brand){
super(); //Call CellPhone's constructor
this.brand = brand;
}
...
}
But this is not OK
class SmartPhone extends CellPhone{
constructor(brand){
this.brand = brand;
}
...
}
//ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
Certainly any parent’s method can be accessed using super.<method>
syntax:
class SmartPhone extends CellPhone{
constructor(brand){..}
call(person){
if (person.hasVideo){
super.call(person); //Trigger call() of super-class
}
else{
this.callVideo(person);
}
}
}
Here we override the call
method inherited from super-class CellPhone
and call the original call
method of CellPhone
inside SmartPhone
‘s new call
method.
Now the code can be re-written as:
So far so good? Indeed.
Nevertheless, here come the next questions: What are the benefits of Classes? Is there any limitation for using Classes over Functions? Does the appearance of Classes change the core concept of JavaScript from prototype-based to class-based?
Benefits
Classes clearly bring us quite a few benefits, which includes
- Easier way for new developer or class-based language developers to keep up with the code base.
- Built-in constructors are subclassable.
- External library for inheritance is not needed anymore and code becomes more portable and easier to maintain between frameworks.
- Creating a foundation for advanced features in the future such as mixins, immutable instances, etc.
- Code system organizing and readability (very important for me 😅!)
Certainly, nothing comes with only benefits, let’s look at Classes’ limitation, shall we?
Warnings / Limitations
- As stated before, Classes are just “special functions”, thus basically a class is type of Function regardless. And Function in JavaScript is Object. Therefore, it did not and will not change the core concept of JavaScript: JavaScript remains strictly prototype-based language.
- Classes, as same as many other cool features of ES6, are just another syntactical sugar for prototypes. It aims to help developers to write lesser, cleaner and more organized code, thereby improving code readability (especially for Java developers who have to dig into JavaScript code 😆). After all, who would want to repeat typing “prototype” for more than 3 times (certainly not me)?
- There is no “hoisting” applied for Classes, unlike Functions, hence:
let phone = new CellPhone();class CellPhone(){...} //ReferenceError: CellPhone is not definedlet phone2 = new CellPhone2();function CellPhone2(){...} //OK
- Class can only be instantiated by using keyword
new
let samplePhone = CellPhone();//TypeError: Class constructor CellPhone cannot be invoked without 'new'
while with function
call it is just simply initialize undefined
unless function
call returns an object.
function CellPhone(){this.battery = 0;}let samplePhone = CellPhone(); //No error
console.log(samplePhone); //undefined
Conclusion
In general, I strongly recommend using Classes over Function in case you want to apply class-based object oriented code style in your system. After all, both of them are the same at core, why not choose new feature that obviously gives you a better control over coding standards?
But again (as usual), if you don’t have to, don’t overuse Classes. Being too organized when not needed is bad. Think about each class you define will actually be instantiated as an object in JavaScript, not staying as a template like in other language.
Keep in mind, JavaScript is not class-based but prototype-based, hence KISS always rules 😘!
How about you, what do you think about Classes? I’d love to hear your thought in the comments below 😺.
More on ES6:
- Set vs Array — What and when?
- Map vs Object — What and when?
- (…) syntax in depth
- var, let, const in depth
- Destructuring me, plz!
- A new JS string with template literals
More on Data Structures:
If you like this post, don’t forget to give me a 👏 below ⏬️ . It will surely motivate me a lot 😊
If you love to read more from me, feel free to check out my articles.
If you’d like to catch up with me sometimes, follow me on Twitter | Facebook or simply visit my portfolio website.