left-icon

ECMAScript 6 Succinctly®
by Matthew Duffield

Previous
Chapter

of
A
A
A

CHAPTER 9

Classes

Classes


Classes in JavaScript provide a great way to reuse code. Up until now, there has never been an easier way to support both classes and inheritance in a clean manner. We will take a look at both of these now.

Class definition

Consider the following Animal class in the code-listing-127.js file:

Code Listing: 127

export class Animal {

  constructor(name) {

    this.name = name;

  }

  greeting(sound) {

    return `A ${this.name} ${sound}`;

  }

  static echo(msg) {

    console.log(msg);

  }  

}

Just like functions, we can export classes to be loaded in another module. We are declaring the Animal class. We have a constructor that takes in a single parameter with the name name. Finally, we have a single function called greeting that takes in a single parameter called sound. The greeting function uses the new string interpolation syntax to create a custom message when called.

I am sure you have noticed the static function echo. The static keyword allows us to mark a function as static. This allows us to reference the function via the class name instead of an instance.

Let’s take a look at the AnimalClient class in the code-listing-128.js file:

Code Listing: 128

import {Animal} from './code-listing-127';

export class AnimalClient {

  constructor() {

    this.animal = new Animal("Dog");

    console.log(this.animal.greeting("barks"));

  }

}

let ac = new AnimalClient();

Animal.echo("roof, roof");

As we have seen before, we are importing the animal class from the code-listing-126.js file. Next, we are constructing an AnimalClient class that creates a new Animal instance inside its constructor and also renders the greeting function out to the console. Since we are testing this code, we have a single line after the class to kick everything off and get things going as well as a call to the static function echo on the Animal class. Even though we are using AnimalClient from the same file, we export the class in order for it to be accessed from another file.

The following is the output from the preceding code:

Code Listing: 129

A Dog barks

roof, roof

Class inheritance/base class access

Now that we understand what the class syntax looks like, let’s extend the Animal class and create another one that inherits from it. Consider the following code from the code-listing-129.js file:

Code Listing: 130

export class Animal {

  constructor(name) {

    this.name = name;

  }

  greeting(sound) {

    return `A ${this.name} ${sound}`;

  }

  static echo(input) {

    console.log(input);

  }

}

export class Canine extends Animal {

  constructor() {

    super("canine");

  }

  static echo() {

    super.echo("bow wow");

  }

}

The Animal class hasn’t changed, but we have added another class to the file called Canine. As you might expect, this class extends the Animal class. This passes in the string canine to the base constructor using the super keyword. We can also use super to access functions and properties off of the base class. This is illustrated in the example of the echo function calling the base implementation and passing the string bow wow.

Getter/setter

Now let’s look at getters and setters. Consider the Animal class in the code-listing-131.js file:

Code Listing: 131

export class Animal {

  constructor(name) {

    this.name = name;

    this.color = "brown";

  }

  get color() {

    return this._color;

  }

  set color(value) {

    this._color = value;

  }

  toString() {

    return console.log(`I am a ${this.name}.  I am ${this.color} in color.`);

  }

  static echo(input) {

    console.log(input);

  }

}

As you can see, we are using two new keywords: get and set. This allows us to provide getters and setters by wrapping other variables or performing other operations. This can be very powerful and allows you to centralize all access to your variables.

Let’s look at how this is used in the AnimalClient class in the code-listing-132.js file:

Code Listing: 132

import {Animal} from './code-listing-131';

export class AnimalClient {

  constructor() {

    this.animal = new Animal("dog");

    this.animal.toString();

  }

}

let ac = new AnimalClient();

Finally, here is the output from the preceding code example:

Code Listing: 133

I am a dog.  I am brown in color.

Subclassing built-ins

Until ES6, subclassing built-ins has been extremely difficult. Consider the following example that does not use built-ins:

Code Listing: 134

function SuperCtor(arg1) {

  ...

}

function SubCtor(arg1, arg2) {

  SuperCtor.call(this, arg1);

}

SubCtor.prototype = Object.create(SuperCtor.prototype);

This is the prototypical way to subclass another object. However, when we deal with built-ins like Array, Date, and DOM Element(s), this is nearly impossible. In JavaScript, if you invoke a built-in constructor, two steps happen internally with every function:

  1. Allocation – creating an instance inst, an object whose prototype is C.prototype (if the value is not an object, use Object.prototype).
  2. Initialization – initializes inst via C.call(inst, arg1, arg2, …). If the result of that call is an object, return it. Otherwise, return inst.

If we were to use the same subclass pattern previously shown, it simply wouldn’t work. However, with ES6, we now have the ability to subclass built-ins.

Object construction for a function named Ctor now uses two phases, both of which are virtually dispatched:

  • Call Ctor [@@create] to allocate the object, installing any special behavior.
  • Invoke constructor on the new instance to initialize.

The known @@create symbol is available via Symbol.create. Built-ins now expose @@create explicitly.

Consider the following code example:

Code Listing: 135

// Pseudo-code of Array

class Array {

  constructor(...args) { /* ... */ }

  static [Symbol.create]() {

      // Install special [[DefineOwnProperty]]

      // to magically update 'length'

  }

}

// User code of Array subclass

class MyArray extends Array {

  constructor(...args) { super(...args); }

}

// Two-phase 'new':

// 1) Call @@create to allocate object

// 2) Invoke constructor on new instance

var arr = new MyArray();

arr[1] = 12;

console.log(arr.length == 2);

console.log(arr);

The first class is an example of what the native JavaScript Array class would look like. What is important here is understanding that by using the @@create symbol we can extend the built-in class. The code required to create your own Array is trivial, although it does not do anything different.

The following is the output of the preceding code:

Code Listing: 136

true

[ , 12 ]

The comma in the output indicates that the length was indeed 12 by returning the result of evaluating the length in a Boolean expression. It also renders out to the console the array where we can easily see that the first index position has nothing assigned.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.