ECMAScript 2015 or more well known as ES6 is the next specification for JavaScript. ES6 brings lots of new features to JavaScript including new syntax improvements. In this post, I am going to cover the new Class
syntax.
Object-Oriented Programming (OOP) can be a great way to organize your projects. Introduced with ES6, the javascript class syntax makes OOP easier.Before ES6 class were introduced, if you wanted to create a class using OOP, you had to use code like
function Cat(name) {
this.name = name;
}
var misty = new Cat('Misty');
console.log(misty.name); // 'Misty'
But now after the introduction of ES6, we have new reserved keyword 'Class' with a constructor statement, so the ES6 equivalent of our Cat
function constructor would be the following:
// ES6 Class syntax with constructor
class Cat{
constructor(name) {
this.name = name;
}
}
let misty = new Cat('Misty');
// Outputs 'Misty'
console.log(misty.name);
The new syntax gives us a dedicated constructor statement that runs on object creation. Constructors are helpful for any object initialization logic.
Class declarations are not hoisted
Function declarations are hoisted: When entering a scope, the functions that are declared in it are immediately available – independently of where the declarations happen.
That means that you can call a function that is declared later:
foo(); // works, because `foo` is hoisted
function foo() {}
In contrast, class declarations are not hoisted.
Therefore, a class only exists after execution reached its definition and it was evaluated. Accessing it beforehand leads to a ReferenceError
:
new Foo(); // ReferenceError
class Foo {}
The reason for this limitation is that classes can have an extends
clause whose value is an arbitrary expression. That expression must be evaluated in the proper “location”, its evaluation can’t be hoisted.
Methods
Let's look at adding functions.In ES5 we would had something like this:
// ES5 adding a method to the Cat prototype
Cat.prototype.walk = function() {
console.log(this.name + ' is walking.');
}
var misty = new Cat('Misty');
misty.walk(); // Outputs 'Misty is walking.'
ES6 offers a more clear syntax to achieve the above goal
class Cat{
constructor(name) {
this.name = name;
}
//method of ES6 defined
walk() {
console.log(this.name + ' is walking.');
}
}
let misty= new Cat('Misty');
console.log(misty.name);
// Outputs 'Misty is walking'
Static methods
Static methods are methods which are added to the class itself and are not attached to instances of the class. To create a static method, you prefix a method definition with static
:
class Cat{
constructor (name) {
this.name = name;
}
static isCat (animal) {
return Object.getPrototypeOf(animal).constructor.name === this.name;
}
}
var misty= new Cat('Misty');
misty.isCat; // undefined
Cat.isCat(misty); // true
Get & Set
ES6 classes brings a new syntax for getters and setters on object properties.
Getters and setters in ES6 serve the same purpose that they do in other languages, including ES5. ES5 already allows getters and setters via Object.defineProperty
, though they're less clean and more cumbersome to use.
Effectively, getters and setters allow you to use standard property access notation for reads and writes while still having the ability to customize how the property is retrieved and mutated without needed explicit getter and setter methods.
So lets create a get
and
set
for our name property.
// ES6 getter and settter
class Cat{
constructor(name) {
this._name = name;
}
get name() {
return this._name.toUpperCase();
}
set name(newName) {
// validation could be checked here such as only allowing non numerical values
this._name = newName;
}
walk() {
console.log(this._name + ' is walking.');
}
}
let misty = new Cat('Misty');
console.log(misty.name);
// Outputs 'MISTY'
In our class above we have a getter and setter for our name property. We use ‘_’ convention to create a backing field to store our name property. With out this every time get or set is called it would cause a stack overflow. The get would be called and which would cause the get to be called again over and over causing a infinite loop.
Something to note is that our backing field
this._name
is not private. Someone could still access
bob._name
and retrieve the property. To achieve private state on objects you would use ES6 symbol
and
module
to create true encapsulation and private state.
Inheritance
In ES6 classes inheritance much easier.With constructor functions, there were a few manual steps that had to be taken to properly implement inheritance. Prveiosuly, in es5 we had to code for inheritance like
function Pet (name) {
this.name = name;
}
function Cat(name, tricks) {
// invoke the Pet constructor function passing in our newly created this object
// and any required parameters
Pet.call(this, name);
// add additional parameters
this.tricks = tricks;
}
// inherit the parent's prototype functions
Cat.prototype = Object.create(Pet.prototype);
// reset our child class constructor function
Cat.prototype.constructor = Cat;
Now with new ES6 sntax, using extends
keyword we can inherit the base class, so we no longer have to manually copy the parent class's prototype functions and reset the child class's constructor.
// create parent class
class Pet {
constructor (name) {
this.name = name;
}
}
// create child class and extend our parent class
class Cat extends Pet {
constructor (name, tricks) {
// invoke our parent constructor function passing in any required parameters
super(name);
this.tricks = tricks;
}
}
You can see the es6 syntax offers a clean syntax for prototypal inheritance. One detail you may notice is the super()
keyword. The super keyword lets us call the parent object that is being inherited. It is good advice to avoid this as this can cause an even tighter coupling between your objects but there are occasions where it is appropriate to use. In this case it can be used in the constructor to assign to the super constructor.
So, our complete example code using the above explained Inheritance, Classes and methods of ES6 will be
class Pet {
constructor(name) {
this._name = name;
}
get name() {
return this._name;
}
set name(newName) {
this._name = newName;
}
walk() {
console.log(this._name + ' is walking.');
}
}
class Cat extends Pet {
constructor(name, activity) {
super(name);
this._activity = activity;
}
get PetActivity() {
return this._activity;
}
set PetActivity(activity) {
this._activity = activity;
}
writeEating() {
console.log(this._name + ' is cat & she is ' + this._activity + '.');
}
}
let misty = new Pet('Pet');
misty.walk();
let catty = new Cat('Misty', 'eating');
catty.walk();
catty.writeEating();
console.log(catty.name);
here is the working fiddle link https://jsfiddle.net/1bk6wvtz/10/
Output Image:
ES6 classes Pros and Cons
ES6 classes provide a few clear benefits:
- They are backward-compatible with much of the current code.
- Compared to constructors and constructor inheritance, classes make it easier for beginners to get started.
- Subclassing is supported within the language.
- Built-in constructors are subclassable.
- No library for inheritance is needed, anymore; code will become more portable between frameworks.
- They provide a foundation for advanced features in the future: traits (or mixins), immutable instances, etc.
- They help tools that statically analyze code (IDEs, type checkers, style checkers, etc.).
Complaints about ES6 Classes
- ES6 classes obscure the true nature of JavaScript inheritance
- Classes provide only single inheritance
- You can override the default result returned by the
new
operator, by returning an object from theconstructor
method of a class. - Due to its built-in modules and classes, ES6 makes it easier for IDEs to refactor code. Therefore, going from
new
to a function call will be simple. Obviously that doesn’t help you if you don’t control the code that calls your code, as is the case for libraries.Classes lock you in, due to mandatory
If you want to instantiate a class, you are forced to usenew
in ES6. That means that you can’t switch from a class to a factory function without changing the call sites.
- You can override the default result returned by the