Chapter 8. Classes and Modules

In this chapter, we will explore some of the most interesting features introduced in ES6. JavaScript is a prototype-based language and supports prototypical inheritance. In the previous chapter, we discussed the prototype property of an object and how prototypical inheritance works in JavaScript. ES6 brings in classes. If you are coming from traditional object-oriented languages such as Java, you will immediately relate to the well-known concepts of classes. However, they are not the same in JavaScript. Classes in JavaScript are a syntactic sugar over the prototypical inheritance we discussed in the last chapter.

In this chapter, we will take a detailed look at ES6 classes and modules - these are welcome changes to this edition of JavaScript and make Object Oriented Programming (OOP) and inheritance significantly easier.

If you are coming from a traditional object-oriented language, prototypical inheritance may feel a bit out of place for you. ES6 classes offer a more traditional syntax for you to get familiarized with prototypical inheritance in JavaScript.

Before we try and delve deeper into classes, let me show you why you should use the ES6 classes syntax over the prototypical inheritance syntax of ES5.

In this snippet, I am creating a class hierarchy of Person, Employee, and Engineer, pretty straightforward. First, we will see the ES5 prototypical inheritance, which is written as follows:

    var Person = function(firstname) { 
        if (!(this instanceof Person)) { 
            throw new Error("Person is a constructor"); 
        } 
        this.firstname = firstname; 
    }; 
 
    Person.prototype.giveBirth = function() { 
        // ...we give birth to the person 
    }; 
 
    var Employee = function(firstname, lastname, job) { 
        if (!(this instanceof Employee)) { 
            throw new Error("Employee is a constructor"); 
        } 
        Person.call(this, firstname); 
        this.job = job; 
    };  
    Employee.prototype = Object.create(Person.prototype); 
    Employee.prototype.constructor = Employee; 
    Employee.prototype.startJob = function() { 
        // ...Employee starts job 
    }; 
 
    var Engineer = function(firstname, lastname, job, department) { 
        if (!(this instanceof Engineer)) { 
            throw new Error("Engineer is a constructor"); 
        } 
        Employee.call(this, firstname, lastname, job); 
        this.department = department; 
    }; 
    Engineer.prototype = Object.create(Employee.prototype); 
    Engineer.prototype.constructor = Engineer; 
    Engineer.prototype.startWorking = function() { 
        // ...Engineer starts working 
    }; 

Now let's look at the equivalent code using the ES6 classes syntax:

    class Person { 
        constructor(firstname) { 
            this.firsnamet = firstname; 
        } 
        giveBirth() { 
            // ... a person is born 
        } 
    } 
 
    class Employee extends Person { 
        constructor(firstname, lastname, job) { 
            super(firstname); 
            this.lastname = lastname; 
            this.position = position; 
        } 
 
         startJob() { 
            // ...Employee starts job 
        } 
    } 
 
    class Engineer extends Employee { 
        constructor(firstname, lastname, job, department) { 
            super(firstname, lastname, job); 
            this.department = department; 
        } 
 
        startWorking() { 
            // ...Engineer starts working 
        } 
    } 

If you observe the two preceding code snippets, it will be obvious to you that the second example is pretty neat. If you already know Java or C#, you will feel right at home. However, one important thing to remember is that classes do not introduce any new object-oriented inheritance model to the language, but bring in a much nicer way to create objects and handle inheritance.

Defining classes

Under the hood, classes are special functions. Just like you can define functions using function expressions and declarations, you can define classes as well. One way to define classes is using class declaration.

You can use the class keyword and the name of the class. This syntax is very similar to that of Java or C#:

    class Car { 
      constructor(model, year){ 
        this.model = model; 
        this.year = year; 
      } 
    } 
    console.log(typeof Car); //"function" 

To establish the fact that classes are a special function, if we get the typeof the Car class, we will get a function.

There is an important distinction between classes and normal functions. While normal functions are hoisted, classes are not. A normal function is available immediately when you enter a scope in which it is declared; this is called hoisting, which means that a normal function can be declared anywhere in the scope, and it will be available. However, classes are not hoisted; they are available only after they are declared. For a normal function, you can say:

    normalFunction();   //use first 
    function normalFunction() {}  //declare later 

However, you cannot use the class before declaring it, for example:

    var ford = new Car(); //Reference Error 
    class Car {} 

The other way to define a class is to use a class expression. A class expression, like a function expression, may or may not have a name.

The following example shows an anonymous class expression:

    const Car = class { 
      constructor(model, year){ 
        this.model = model; 
        this.year = year; 
      } 
    } 

If you name the class expression, the name is local to the class's body and not available outside:

    const NamedCar = class Car{ 
      constructor(model, year){ 
        this.model = model; 
        this.year = year; 
      } 
      getName() { 
          return Car.name; 
      } 
    } 
    const ford = new NamedCar(); 
    console.log(ford.getName()); // Car 
    console.log(ford.name); // ReferenceError: name is not defined 

As you can see, here, we will give a name to the Car class. This name is available within the body of the class, but when we try to access it outside the class, we get a reference error.

You cannot use commas while separating members of a class. Semicolons are valid though. This is funny as ES6 ignores semicolons and there is a raging debate about using semicolons in ES6. Consider the following code snippet as an example:

    class NoCommas { 
      method1(){} 
      member1;  //This is ignored and can be used to 
        separate class members 
      member2,  //This is an error 
      method2(){} 
    } 

Once defined, we can use classes via a new keyword and not a function call; here's the example:

    class Car { 
      constructor(model, year){ 
        this.model = model; 
        this.year = year; 
      } 
    } 
    const fiesta = new Car('Fiesta','2010'), 

Constructor

We have used the constructor function in the examples so far. A constructor is a special method used to create and initialize an object created with the class. You can have only one constructor in a class. Constructors are a bit different from the normal constructor functions. Unlike normal constructors, a class constructor can call its parent class constructor via super(). We will discuss this in detail when we look at inheritance.

Prototype methods

Prototype methods are prototype properties of the class, and they are inherited by instances of the class.

Prototype methods can also have getter and setter methods. The syntax of getters and setters is the same as ES5:

    class Car { 
      constructor(model, year){ 
        this.model = model; 
        this.year = year; 
      } 
      get model(){ 
        return this.model 
      } 
   
      calculateCurrentValue(){ 
        return "7000" 
      } 
    } 
    const fiesta = new Car('Fiesta','2010') 
    console.log(fiesta.model) 

Similarly, computed properties are also supported. You can define the name of the method using the expression. The expression needs to be put inside square brackets. We discussed this shorthand syntax in earlier chapters. The following are all equivalent:

    class CarOne { 
        driveCar() {} 
    } 
    class CarTwo { 
        ['drive'+'Car']() {} 
    } 
    const methodName = 'driveCar'; 
    class CarThree { 
        [methodName]() {} 
    } 

Static methods

Static methods are associated with the class and not with an instance of that class (object). In other words, you can only reach a static method using the name of the class. Static methods are invoked without instantiating the class and they cannot be called on an instance of a class. Static methods are popular in creating utility or helper methods. Consider the following piece of code:

    class Logger { 
      static log(level, message) { 
        console.log(`${level} : ${message}`) 
      } 
    } 
    //Invoke static methods on the Class 
    Logger.log("ERROR","The end is near") //"ERROR : The end is near" 
 
    //Not on instance 
    const logger = new Logger("ERROR") 
    logger.log("The end is near")     //logger.log is not a function 

Static properties

You may ask-well, we have static methods, what about static properties? In the hurry of getting ES6 ready, they did not add static properties. They will be added in future iterations of the language.

Generator methods

We discussed hugely useful generator functions a few chapters back. You can add generator functions as part of class, and they are called generator methods. A generator method is useful because you can define their key as Symbol.iterator. The following example shows how generator methods can be defined inside a class:

    class iterableArg { 
        constructor(...args) { 
            this.args = args; 
        } 
        * [Symbol.iterator]() { 
            for (const arg of this.args) { 
                yield arg; 
            } 
        } 
    } 
 
    for (const x of new iterableArg('ES6', 'wins')) { 
        console.log(x); 
    } 
 
    //ES6 
    //wins 
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.93.210