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
1. Inheriting constructors & properties
2. 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.
babel/cli
babel/core
babel/preset-env
babel/plugin-proposal-class-properties
babel/plugin-proposal-private-methods
babel/register
Steps for the project configuration:
npm init
npm install --save-dev mocha chai
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.
{
"presets": [
[
"@babel/preset-env"
]
],
"plugins": [["@babel/plugin-proposal-private-methods", {"loose": true}],
["@babel/plugin-proposal-class-properties" , {"loose": true}]]
}
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.
class Customer {
}
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.
class Customer {
constructor() {
console.log("\n");
console.log("------------start inside the constructor-----------");
console.log("called automatically when a new instance is created");
console.log("-------------end inside the constructor------------");
}
}
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.
import { Customer} from '../constructors/learning-constructors';
import { expect } from 'chai';
describe("Test if constructors are called when a new instance is created",
() => {
it('returns instanceof customer', () => {
let customer = new Customer();
expect(customer).to.be.an('object');
expect(customer).to.be.an.instanceOf(Customer);
});
});
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++.
class Customer{
#_firstName;
#_lastName;
#_isPrimeMember;
constructor(firstName, lastName, isPrimeMember){
this.#_firstName = firstName;
this.#_lastName = lastName;
this.#_isPrimeMember = isPrimeMember;
}
get firstName(){
return this.#_firstName;
}
set firstName(value){
this.#_firstName = value;
}
get lastName(){
return this.#_lastName;
}
set lastName(value){
this.#_lastName = value;
}
get isPrimeMember(){
return this.#_isPrimeMember;
}
}
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.
import { Customer } from '../getters-setters/getter-setters';
import { expect } from 'chai';
describe("Test the getters and setters of the Customer class",
() => {
let firstName = "Jin", lastName = "Necesario", isPrimeMember = true;
let customer = new Customer(firstName, lastName, isPrimeMember);
it('check wheter we have a valid instance', () => {
//let us check the customer object
expect(customer).to.be.an('object', 'customer is an object');
expect(customer).to.be.an.instanceOf(Customer, 'customer is an instance of the Customer class');
});
it('Checks the object if has valid getters', () => {
//let us check if the customer object does have the correct values
expect(customer.firstName).to.be.a('string').and.equal(firstName, `Customer's firstname is ${firstName}`);
expect(customer.lastName).to.be.a('string').and.equal(lastName, `Customer's firstname is ${lastName}`);
expect(customer.isPrimeMember).to.be.a('boolean').and.equal(isPrimeMember, `Customer's firstname is ${isPrimeMember}`);
});
it('Checks whether the object if has valid setters', () => {
//let us reassign new values for the customer instance
customer = { firstName: "Scott", lastName: "Summers", isPrimeMember: false};
expect(customer.firstName).to.be.a('string').and.equal('Scott', `Customer's firstname is ${firstName}`);
expect(customer.lastName).to.be.a('string').and.equal('Summers', `Customer's firstname is ${lastName}`);
expect(customer.isPrimeMember).to.be.a('boolean').and.equal(false, `Customer's firstname is ${isPrimeMember}`);
});
});
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.
class Customer {
static MinAgeForEntry = 20;
constructor({firstName, lastName, birthDate, country}){
this.firstName = firstName;
this.lastName = lastName;
this.birthDate = birthDate;
this.country = country;
}
}
Let's create a test for this class that has a static property and different instance properties.
import { Customer } from '../class-with-methods/class-with-properties';
import { expect } from 'chai';
describe("Test if class have the following properties firstName,lastName,birthDate and country",
() => {
let customer = new Customer({ firstName: "Jin", lastName: "Necesario", birthDate: "1/1/2000", country: "PHP" });
it('check if the customer has a static property and check its value', () => {
expect(Customer).itself.to.have.property("MinAgeForEntry")
.to.be.equal(20, '20 should be the value');
});
it('check if the customer object have the following properties firstName, lastName,birthDate and country', () => {
expect(customer).to.have.property("firstName");
expect(customer).to.have.property("lastName");
expect(customer).to.have.property("birthDate");
expect(customer).to.have.property("country");
});
it('check the properties values', () => {
expect(customer.firstName).to.be.equal("Jin");
expect(customer.lastName).to.be.equal("Necesario");
expect(customer.birthDate).to.be.equal("1/1/2000");
expect(customer.country).to.be.equal("PHP");
});
});
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.
class Customer {
constructor({firstName, lastName, birthDate, country}){
this.firstName = firstName;
this.lastName = lastName;
this.birthDate = birthDate;
this.country = country;
}
static isLeapYear(){
let year = new Date().getFullYear();
return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
}
getFullName(){
return `Mr./Ms. ${this.lastName}, ${this.firstName}`
}
getCustomerCompleteInfo(){
return `Mr./Ms. ${this.lastName}, ${this.firstName}`
+ ` was born in ${this.country} on ${this.birthDate}`
+ ` and currently ${new Date().getFullYear() - new Date(this.birthDate).getFullYear()} years of age.`
}
}
Let's create a test for this class that will check the values of both static and instance methods.
import { Customer } from '../class-with-methods/class-with-methods';
import { expect } from 'chai';
describe("Test if class have the following methods getFullName, getCustomerCompleteInfo & isLeapYear",
() => {
let customer = new Customer({firstName : "Jin", lastName : "Necesario", birthDate : "1/1/2000", country :"PHP"});
it('checks the methods', () => {
let completeInfo = 'Mr./Ms. Necesario, Jin was born in PHP on 1/1/2000 and currently 20 years of age.';
//instance methods
expect(customer.getFullName()).to.be.equal("Mr./Ms. Necesario, Jin");
expect(customer.getCustomerCompleteInfo()).to.be.equal(completeInfo);
//static methods
expect(Customer.isLeapYear()).to.be.a('boolean').to.be.oneOf([true,false]);
});
});
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.
class Person {
constructor(firstName, lastName, age){
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
}
class Customer extends Person{
constructor(firstName, lastName, age, isPrimeMember){
super(firstName, lastName, age);
this.isPrimeMember = isPrimeMember;
}
}
export { Person, Customer}
Let's try to test if we have correctly inherited the parent class.
import { Person, Customer } from '../inheritance/inheritance-constructor';
import { expect } from 'chai';
describe("Test parent constructor",
() => {
let customer = new Customer("Jin", "Necesario", 120, true);
it('Test if have inherited the properties that was initialized via super', () => {
//check if customer is an instance of Person
expect(customer).to.be.an('object');
expect(customer).to.be.instanceOf(Person);
//check if customer instance have inherited the properties of the Person class
expect(customer).to.have.property("firstName");
expect(customer).to.have.property("lastName");
expect(customer).to.have.property("age");
});
});
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.
class Person {
speak(){
return "I'm a person speaking";
}
jump(){
return "I jump high";
}
}
class Customer extends Person{
speak(){
return `When person speaks: ${super.speak()}. When the customer speaks: Hi! I'm a customer`;
}
jump() {
return "Overriding jump, a customer doesn't jump";
}
}
Let's try to test if we have correctly called the parent method by using the super keyword.
import { Customer} from '../inheritance/inheritance-methods';
import { expect } from 'chai';
describe("Test inheritance methods",
() => {
it('Test the methods', () => {
let customer = new Customer();
let resultOfPersonSpeaking = customer.speak();
let resultOfPersonJumping = customer.jump();
expect(resultOfPersonSpeaking)
.to.be
.equal("When person speaks: I'm a person speaking. When the customer speaks: Hi! I'm a customer");
expect(resultOfPersonJumping)
.to.be
.equal("Overriding jump, a customer doesn't jump");
});
});
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!
Source:Paper.li
Comments