Object Oriented Programming JavaScript - ES6

Introduction 


Before ES6, JavaScript does not really support classes and classical inheritance as the primary way of defining similar and related objects when it was created. This was very confusing back then, especially if you are coming from a C#, Java, C++ background. Moreover, the closest equivalent to a class before ES6 was creating a constructor-function and then assigning methods to the constructor’s prototype, an approach which typically called for creating a custom type.
 
However, today due to ES6, developers can now create their classes. We are going to tackle that in this article. Lastly, I still believe in more code samples to eventually learn the language even better, hence, we are going to focus more on the syntax to familiarize ourselves in this OOP core concepts.
 
Here are the following subjects that we are going to discuss and show some code samples.
  • Class creation
  • Class constructors
  • Class getters and setters
  • Class properties and static properties
  • Class methods and static methods
  • Inheritance
    • Inheriting constructors & properties
    • Inheriting methods

Prerequisites, Installations, and Configuration

 
We are going to use Node.js as our platform to explore the concepts of OOP using JavaScript/ES6. Therefore, Node.js should be installed within your operating system. 
 
Project package dependencies that need to be installed.
  • Mocha 
  • Chai 
  • Babel Plugins

    • babel/cli
    • babel/core
    • babel/preset-env
    • babel/plugin-proposal-class-properties
    • babel/plugin-proposal-private-methods
    • babel/register
Steps for the project configuration:
  1. npm init 
  2. npm install --save-dev mocha chai 
  3. npm install --save-dev @babel/cli @babel/core @babel/preset-env @babel/register @babel/plugin-proposal-class-properties @babel/plugin-proposal-private-methods

Configuration

 
Once you are done with the installation. Don't forget to create a .babelrc file. Your .babelrc file should be similar below. 
  1. {  
  2.     "presets": [  
  3.       [  
  4.         "@babel/preset-env"  
  5.       ]  
  6.     ],  
  7.     "plugins": [["@babel/plugin-proposal-private-methods", {"loose"true}],  
  8.                 ["@babel/plugin-proposal-class-properties" , {"loose"true}]]  
  9.   }   
However, if you are going to download the project file just go to the command line and type "npm install" it will just automatically install all the dependencies that was saved within the package.json file.
 

Class creation

 
In ES6, class declaration looks pretty similar to classes in other languages. Class declarations begin with the class keyword followed by the name of the class. Let's take a look at the example below. 
  1. class Customer {  
  2.       
  3. }  
To create a class in JavaScript/ES6 you just need the keyword class then followed by its name. In our case, the name of the class is "Customer" without the quotes. 

Class constructor


Yes, I know the previous example was very easy to understand. Well, that's how we learn we start with simple examples first as we progress with the hard topics.
 
Now, let's move on with constructors
 
Basically, constructors are executed once we have created a new instance of a class. Another thing to note, JavaScript doesn't support constructor overloading. Therefore you can't have more than one constructor, within your class, or else the JavaScript compiler will throw an error. 
 
Let us an example of a constructor. 
  1. class Customer {  
  2.   
  3.     constructor() {  
  4.   
  5.         console.log("\n");  
  6.         console.log("------------start inside the constructor-----------");  
  7.         console.log("called automatically when a new instance is created");  
  8.         console.log("-------------end inside the constructor------------");  
  9.     }  
  10. }  
Let's try to test again the Customer class if the constructor is really executed when we have created a new instance of the Customer class. 
  1. import { Customer} from '../constructors/learning-constructors';  
  2. import { expect } from 'chai';  
  3.   
  4. describe("Test if constructors are called when a new instance is created",  
  5.     () => {  
  6.         it('returns instanceof customer', () => {  
  7.   
  8.             let customer = new Customer();  
  9.   
  10.             expect(customer).to.be.an('object');  
  11.             expect(customer).to.be.an.instanceOf(Customer);  
  12.   
  13.         });  
  14. });  
Output
 
 
Wasn't that great? We have seen how constructors work. Now, you might be thinking that there could be a hack or another way to have a constructor overloading.
 
Well, in my experience you can use object destructuring assignment as parameters for the constructor. That's the closest thing to the constructor overloading, in my opinion. Don't worry we are going to use that in the class properties section. If you want to learn more about it, please this my post at C# corner about JavaScript Destructing Assignment.
 
Moreover; there are many ways to do this is just one of them. Here are some ways of doing it: passing an object,  using default parameters, and object destructuring assignment.
 

Class getters and setters

 
Now, we have seen how to declare a class and understand the constructor.
 
In this section, we are going to see how to create these getters and setters just like those getters and setters in Java/C#/C++.  
  1.  class Customer{  
  2.  
  3.     #_firstName;   
  4.     #_lastName;  
  5.     #_isPrimeMember;  
  6.   
  7.     constructor(firstName, lastName,  isPrimeMember){  
  8.         this.#_firstName = firstName;  
  9.         this.#_lastName = lastName;  
  10.         this.#_isPrimeMember = isPrimeMember;  
  11.     }  
  12.   
  13.     get firstName(){  
  14.         return this.#_firstName;  
  15.     }  
  16.   
  17.     set firstName(value){  
  18.         this.#_firstName = value;  
  19.     }  
  20.   
  21.     get lastName(){  
  22.         return this.#_lastName;  
  23.     }  
  24.   
  25.     set lastName(value){  
  26.         this.#_lastName = value;  
  27.     }  
  28.   
  29.     get isPrimeMember(){  
  30.   
  31.         return this.#_isPrimeMember;  
  32.     }  
  33. }  
Probably, you are asking yourself what's with the pound sign (#). It is basically saying to the JS compiler that we have a private member within our class. 
 
There are many ways to mimic private members within the JavaScript class, and this is one way.
 
Now, in order to fully demonstrate getters and setters, we really need some kind of private or backing fields so we could appreciate the getters and setters.  
 
One thing to point out: this feature is not yet totally supported, that's why we are dependent on the following packages babel/plugin-proposal-class-properties and babel/plugin-proposal-private-methods, which makes this feature possible.
 
Let's see how we can test this class. 
  1. import { Customer } from '../getters-setters/getter-setters';  
  2. import { expect } from 'chai';  
  3.   
  4. describe("Test the getters and setters of the Customer class",  
  5.     () => {  
  6.   
  7.         let firstName = "Jin", lastName = "Necesario", isPrimeMember = true;  
  8.   
  9.         let customer = new Customer(firstName, lastName, isPrimeMember);  
  10.   
  11.         it('check wheter we have a valid instance', () => {  
  12.             //let us check the customer object  
  13.             expect(customer).to.be.an('object''customer is an object');  
  14.             expect(customer).to.be.an.instanceOf(Customer, 'customer is an instance of the Customer class');  
  15.         });  
  16.   
  17.         it('Checks the object if has valid getters', () => {  
  18.   
  19.             //let us check if the customer object does have the correct values  
  20.             expect(customer.firstName).to.be.a('string').and.equal(firstName, `Customer's firstname is ${firstName}`);  
  21.             expect(customer.lastName).to.be.a('string').and.equal(lastName, `Customer's firstname is ${lastName}`);  
  22.             expect(customer.isPrimeMember).to.be.a('boolean').and.equal(isPrimeMember, `Customer's firstname is ${isPrimeMember}`);  
  23.   
  24.         });  
  25.   
  26.         it('Checks whether the object if has valid setters', () => {  
  27.   
  28.             //let us reassign new values for the customer instance  
  29.             customer = { firstName: "Scott", lastName: "Summers", isPrimeMember: false};  
  30.             
  31.             expect(customer.firstName).to.be.a('string').and.equal('Scott', `Customer's firstname is ${firstName}`);  
  32.             expect(customer.lastName).to.be.a('string').and.equal('Summers', `Customer's firstname is ${lastName}`);  
  33.             expect(customer.isPrimeMember).to.be.a('boolean').and.equal(false, `Customer's firstname is ${isPrimeMember}`);  
  34.   
  35.         });  
  36.     });  
Output
 
 

Class Properties and Static Properties

 
Of course, it won't be complete without dealing with properties. In this section, we are going to see how to access those properties of a class.
 
Like in other programming languages, this keyword is a reference to the current object - the object whose constructor is being called. However, the properties in JavaScript are by default public because you can access them directly. Don't forget to use the keyword static to identify a property as a static one. 
 
One thing to point out, we have used the object destructuring assignment as the usage of the constructor.  
 
Let's see this in action. 
  1. class Customer {  
  2.   
  3.     static MinAgeForEntry = 20;  
  4.       
  5.     constructor({firstName, lastName, birthDate, country}){  
  6.         this.firstName = firstName;  
  7.         this.lastName = lastName;  
  8.         this.birthDate = birthDate;  
  9.         this.country = country;  
  10.     }  
  11. }  
Let's create a test for this class that has a static property and different instance properties.
  1. import { Customer } from '../class-with-methods/class-with-properties';  
  2. import { expect } from 'chai';  
  3.   
  4. describe("Test if class have the following properties firstName,lastName,birthDate and country",  
  5.     () => {  
  6.   
  7.         let customer = new Customer({ firstName: "Jin", lastName: "Necesario", birthDate: "1/1/2000", country: "PHP" });  
  8.   
  9.         it('check if the customer has a static property and check its value', () => {  
  10.   
  11.             expect(Customer).itself.to.have.property("MinAgeForEntry")
  12.                             .to.be.equal(20, '20 should be the value');  
  13.         });  
  14.   
  15.         it('check if the customer object have the following properties firstName, lastName,birthDate and country', () => {  
  16.   
  17.             expect(customer).to.have.property("firstName");  
  18.             expect(customer).to.have.property("lastName");  
  19.             expect(customer).to.have.property("birthDate");  
  20.             expect(customer).to.have.property("country");  
  21.   
  22.         });  
  23.   
  24.         it('check the properties values', () => {  
  25.   
  26.             expect(customer.firstName).to.be.equal("Jin");  
  27.             expect(customer.lastName).to.be.equal("Necesario");  
  28.             expect(customer.birthDate).to.be.equal("1/1/2000");  
  29.             expect(customer.country).to.be.equal("PHP");  
  30.   
  31.         });  
  32.     });  
As you can see from the tests, once we have passed an argument with values within the constructor and set the properties with those values you can basically access with ease. However, if you want some kind of protection you can go back to the previous example which shows the private fields of a class. 
 
Output
 
 

Class Methods and Static Methods

 
You probably are already familiar with functions, functions are also called methods, just remember that they are interchangeable. 
 
When defining methods there is no special keyword just define its name and construct its body. However, when defining a static method you need to put the static keyword first then its name.
 
Let's see an example below. 
  1. class Customer {  
  2.   
  3.     constructor({firstName, lastName, birthDate, country}){  
  4.         this.firstName = firstName;  
  5.         this.lastName = lastName;  
  6.         this.birthDate = birthDate;  
  7.         this.country = country;  
  8.     }  
  9.   
  10.     static isLeapYear(){  
  11.   
  12.         let year = new Date().getFullYear();  
  13.           
  14.         return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);  
  15.     }  
  16.   
  17.     getFullName(){  
  18.         return `Mr./Ms. ${this.lastName}, ${this.firstName}`  
  19.     }  
  20.   
  21.     getCustomerCompleteInfo(){  
  22.         return `Mr./Ms. ${this.lastName}, ${this.firstName}`   
  23.                + ` was born in ${this.country} on ${this.birthDate}`   
  24.                + ` and currently ${new Date().getFullYear() - new Date(this.birthDate).getFullYear()} years of age.`  
  25.     }  
  26. }  
Let's create a test for this class that will check the values of both static and instance methods.
  1. import { Customer } from '../class-with-methods/class-with-methods';  
  2. import { expect } from 'chai';  
  3.   
  4. describe("Test if class have the following methods getFullName, getCustomerCompleteInfo & isLeapYear",  
  5.     () => {  
  6.   
  7.         let customer = new Customer({firstName : "Jin", lastName : "Necesario", birthDate : "1/1/2000", country :"PHP"});  
  8.   
  9.         it('checks the methods', () => {  
  10.   
  11.             let completeInfo = 'Mr./Ms. Necesario, Jin was born in PHP on 1/1/2000 and currently 20 years of age.';  
  12.   
  13.             //instance methods  
  14.             expect(customer.getFullName()).to.be.equal("Mr./Ms. Necesario, Jin");  
  15.             expect(customer.getCustomerCompleteInfo()).to.be.equal(completeInfo);  
  16.               
  17.             //static methods  
  18.             expect(Customer.isLeapYear()).to.be.a('boolean').to.be.oneOf([true,false]);  
  19.         });  
  20.     });  
Output
 
 

Inheriting constructors & properties

 
In this section, we are going to see how a child class can extend a parent class. It can be done using the extends keyword. Hence, by using the keyword extends, we are basically inheriting the parent class.
 
Don't forget to always call the parent's constructor, even if it is empty when you are inside the child class, or else an error will be thrown. 
  1. class Person {  
  2.     constructor(firstName, lastName, age){  
  3.         this.firstName = firstName;  
  4.         this.lastName = lastName;  
  5.         this.age = age;  
  6.     }  
  7. }  
  8.   
  9. class Customer extends Person{  
  10.       
  11.     constructor(firstName, lastName, age, isPrimeMember){  
  12.           
  13.         super(firstName, lastName, age);  
  14.           
  15.         this.isPrimeMember = isPrimeMember;  
  16.     }  
  17. }  
  18.   
  19. export { Person, Customer}  
Let's try to test if we have correctly inherited the parent class. 
  1. import { Person, Customer } from '../inheritance/inheritance-constructor';  
  2. import { expect } from 'chai';  
  3.   
  4. describe("Test parent constructor",  
  5.     () => {  
  6.         let customer = new Customer("Jin""Necesario", 120, true);  
  7.   
  8.         it('Test if have inherited the properties that was initialized via super', () => {  
  9.   
  10.             //check if customer is an instance of Person  
  11.             expect(customer).to.be.an('object');  
  12.             expect(customer).to.be.instanceOf(Person);  
  13.   
  14.             //check if customer instance have inherited the properties of the Person class  
  15.             expect(customer).to.have.property("firstName");  
  16.             expect(customer).to.have.property("lastName");  
  17.             expect(customer).to.have.property("age");  
  18.         });  
  19.     });  
Output
 
 

Inheriting methods

 
In this section, we are going to see how we can execute or call the parent method. Still, by using the super keyword then by dot (.) then name of the function/method so we can invoke it at the child class. Just a note if you want to fully override the method you can just create a new implementation of that method and of course don't call the parent method. Let's try an example below. 
  1. class Person {  
  2.   
  3.     speak(){  
  4.   
  5.         return "I'm a person speaking";  
  6.     }  
  7.   
  8.     jump(){  
  9.   
  10.         return "I jump high";  
  11.     }  
  12. }  
  13.   
  14. class Customer extends Person{  
  15.       
  16.     speak(){  
  17.   
  18.         return `When person speaks: ${super.speak()}. When the customer speaks: Hi! I'm a customer`;  
  19.     }  
  20.   
  21.     jump() {  
  22.   
  23.         return "Overriding jump, a customer doesn't jump";  
  24.     }  
  25. }  
Let's try to test if we have correctly called the parent method by using the super keyword. 
  1. import { Customer} from '../inheritance/inheritance-methods';  
  2. import { expect } from 'chai';  
  3.   
  4. describe("Test inheritance methods",  
  5.     () => {  
  6.         it('Test the methods', () => {  
  7.   
  8.             let customer = new Customer();  
  9.               
  10.             let resultOfPersonSpeaking  = customer.speak();  
  11.               
  12.             let resultOfPersonJumping = customer.jump();  
  13.   
  14.             expect(resultOfPersonSpeaking)  
  15.                 .to.be  
  16.                 .equal("When person speaks: I'm a person speaking. When the customer speaks: Hi! I'm a customer");  
  17.   
  18.             expect(resultOfPersonJumping)  
  19.                 .to.be  
  20.                 .equal("Overriding jump, a customer doesn't jump");  
  21.   
  22.         });  
  23. });  
Output
 
 

Summary

 
In this article, we have discussed the following:
  • Class creation
  • Class constructors
  • Class getters and setters
  • Class properties and static properties
  • Class methods and static methods
  • Inheritance
    • Inheriting constructors & properties
    • Inheriting methods
I hope you have enjoyed this article as much as I have enjoyed writing it. You can also find the sample code here at GitHub. Until next time, happy programming! Kendo UI jQuery Validator


Similar Articles