Overview
As you might already know javascript is a prototype-based language. It was not designed to object-oriented but as the javascript was getting popular there were different libraries which were trying to extend javascript to be class based. So ES6 introduced class in javascript.
According to MDN:
classes are primarily syntactical sugar over js’s existing prototype-based inheritance. The class syntax does not introduce a new object-oriented inheritance model to JavaScript.
Before diving deep into the javascript class, let us first how different object-oriented concepts were handled through the prototype.
Before ES6.
Functions
were used to replicate class
.
Declaring properties
//Creating new function function individual(name, age){ this.name = name; this.age = age; }
Calling the function
as class
let person1 = new individual('prashant', 23); let person2 = new individual('yogesh', 24);
Declaring methods
//By declaring method inside the function function individual(name, age){ this.name = name; this.age = age; this.details = function(){ return this.name + ' is ' + this.age + ' years old.'; } } //By declaring the method through prototype individual.prototype.details = function(){ return this.name + ' is ' + this.age + ' years old.'; }; //Calling the function let person1 = new individual('prashant', 23); console.log(person1.details()); //"prashant is 23 years old."
After ES6.
There are two ways of declaring a class
1) Class declaration.
2) Class expression.
Class Declaration.
//declaring class class Individual{ //declaring properties inside constructore constructor(name, age){ this.name = name; this.age = age; } //declaring display method details(){ return `${this.name} is ${this.age} years old.`; } }
//creating a person object from individual class let person = new Individual('prashant', 23); //calling the display method console.log(person.details()); //"prashant is 23 years old."
Class Expression.
We can assing the class
expression to the variables.
1) Unnamed class expression
2) Named class expression
//unnamed class expression const Person = class{ //declaring properties inside constructore constructor(name, age){ this.name = name; this.age = age; } //declaring display method details(){ return `${this.name} is ${this.age} years old.`; } } //named class expression const Person = class Individual{ //declaring properties inside constructore constructor(name, age){ this.name = name; this.age = age; } //declaring display method details(){ return `${this.name} is ${this.age} years old.`; } }
//creating a person object from individual class let person1 = new Person('prashant', 23); //calling the display method console.log(person1.details()); //"prashant is 23 years old."
constructor()
should be decalared only once.
As we can see class
works similar to the prototype
based approach. But there are many advantages of using classes.
- Unlike
functions
,class
declarations are not hoisted. Which means variables declared inside are not accessible outside. They remain in temporal dead zone just like let until execution reaches the declaration. - Code inside
class
runs in strict mode. - Methods declared inside
class
are non-enumerable. Unlikeprototype
based where we had to use object.defineProperty() to make method non-enumerable. - Calling the
class
constructor withoutnew
keyword will throw error. Also all the methods decalared insideclass
lack the[[construct]]
method and calling them withnew
keyword will result in error. - Declaring the method name same as
class
name insideclass
will result in error.
Static methods
Static methods are only accessible by parents (class), derived child or instance of the class
cannot access them.
class Person { //declaring properties constructor(name, age){ this.name = name; this.age = age; } //declaring method //accessible by all details(){ console.log(`${this.name} is ${this.age} years old.`); } //static method //accessible only my parent static display(){ console.log(`I am only accessible by parent.`); } }
Input: let person1 = new Person('prashant', 23); person1.details(); Person.display(); person1.display(); Output: "prashant is 23 years old." "I am only accessible by parent." Uncaught TypeError: person1.display is not a function
Accessor Properties
There are two accessor properties which we can use with class
.
1) Set
:- To set the value of any property.
2) Get
:- To get the value of any property.
class Person { //declaring properties constructor(name, age){ this.name = name; this.age = age; } //Set name set nickName(name){ this.name = name; } //get name get nickName(){ return `your name is ${this.name}` } }
Input: let person1 = new Person('Prashant Yadav', 23); console.log(person1.nickName); //set the new nickName person1.nickName = 'Golu'; console.log(person1.nickName); Output: "your name is Prashant Yadav" "your name is Golu"
Computed method names
Class
methods and Accessor properties can computed names, That means their name can be provided at runtime.
let myMethod = 'displayDetail'; class Person { //declaring properties constructor(name, age){ this.name = name; this.age = age; } //declaring a method with the computed name [myMethod](){ return `${this.name} is ${this.age} years old.`; } }
Input: let person = new Person('Prashant Yadav', 23); console.log(person.displayDetail()); Output: "Prashant Yadav is 23 years old."
Accessor properties can also use computed methods.
let myMethod = 'differentName'; class Person { //declaring properties constructor(name, age){ this.name = name; this.age = age; } //Set name set [myMethod ](name){ this.name = name; } //get name get [myMethod ](){ return `your name is ${this.name}` } }
Input: let person1 = new Person('Prashant Yadav', 23); console.log(person1.differentName); //set the new differentName person1.differentName = 'Golu'; console.log(person1.differentName); Output: "your name is Prashant Yadav" "your name is Golu"
Inheritance
Before ES6, implementing inherintance was a tedious process with prototype. Proper inheritance required multiple steps.
function Rectangle(width, height){ this.width = width; this.height = height; } //declaring method to calculate area; Rectangle.prototype.getArea = function(){ return this.height * this.width; } //Extending rectangle to use it as square //using call, apply, bind function Square(length){ Rectangle.call(this, length, length); } //Extending methods Square.prototype = Object.create(Rectangle.prototype, { constructor: { value: Square, enumerable: true, writable: true, configurable: true } });
var square = new Square(3); console.log(square.getArea()); // 9 console.log(square instanceof Square); // true console.log(square instanceof Rectangle); // true
After ES6, we can use extends
keyword to achieve the inheritance.
//Declaring the rectangle class class Rectangle { //intialize the properties constructor(length, width) { this.length = length; this.width = width; } //Method to calculate the area getArea() { return this.length * this.width; } } //Extend the Square to use Rectangle class Square extends Rectangle { constructor(length) { // same as Rectangle.call(this, length, length) super(length, length); } }
const square = new Square(3); console.log(square.getArea()); // 9 console.log(square instanceof Square); // true console.log(square instanceof Rectangle); // true
Points to remember while inheriting the another class
.
- If you inherit any class and declare the
constructor()
method inside it use must declaresuper()
method as well inside theconstructor()
. Failing to do so will result in error. - If there are any
static
methods in the parentclass
then they are also accessible to the inheritedclass
. new.target
can be used to check the current instance of theclass
and where it is getting called from.
Derived class form expression
One of the most powerful features of ES6 class is the ability to derive from a class from an expression.
You can use extends
with any expression as long as the expression resolves to a function with [[Construct]]
and a prototype.
//A function declaration function Rectangle(length, width) { this.length = length; this.width = width; } //Adding method to the function Rectangle.prototype.getArea = function() { return this.length * this.width; }; //Extending the rectangle function class Square extends Rectangle { constructor(length) { super(length, length); } }
Input: let x = new Square(3); console.log(x.getArea()); // 9 console.log(x instanceof Rectangle); // true
With this powerful feature, we can inherit the inbuilt functions as well. The only clause is that they should have [[construct]]
method and a prototype
.
Extending the Array
class MyArray extends Array { // empty } var colors = new MyArray(); colors[0] = "red"; console.log(colors.length); // 1 colors.length = 0; console.log(colors[0]); // undefined
MyArray inherits directly from the Array. As Array has [[construct]]
method as well as a prototype.
class Country extends Array { // use rest operator to grab all the persons constructor(name, ...persons){ //pass all the students to the array super(...persons); this.country = country; } //Method to add new person add(person){ this.push(person); } }
Input: const myCountry = new Country('India', {name: "Prashant", age: 23}, {name: "Yogesh", age: 24}, {name: "Pranav", age: 23}, {name: "Sachin", age: 23}, ); myCountry.add({name: "Aditya", age: 24}); console.log(myCountry[4]); //loop the data for(const persons of myCountry) { console.log(persons); } Output: //myCountry[4] Object { age: 24, name: "Aditya" } //Loop Object { age: 23, name: "Prashant" } Object { age: 24, name: "Yogesh" } Object { age: 23, name: "Pranav" } Object { age: 23, name: "Sachin" } Object { age: 24, name: "Aditya" }
richie says:
There is typo in the example above with the topic Static Methods
person.display()
instead of
Person.display()