US & Canada: 877 849 1850
International: +1 678 648 3113

Accelebrate Blog

ACCELERATED LEARNING, CELEBRATED RESULTS

JavaScript ES2015 Classes and Prototype Inheritance (Part 1 of 2)

What is JavaScript ES2015?

With the finalizing of the ECMA Script 2015 (ES2015) specification in June of last year, the JavaScript community can move towards the implementation of ES2015 in the many JavaScript engines in the marketplace.  ES2015 was formerly known as ES6, but following the pattern of other programming languages, the year is now being used instead of an arbitrary version number.

ES2015 offers many helpful new features and cleaner syntax for existing features. One example of this cleaner syntax is the class keyword, as well as improved syntax for using prototype inheritance in JavaScript applications.

Until ES2015, implementing prototype inheritance with JavaScript was confusing and difficult for many JavaScript developers. The first challenge is to understand how prototype inheritance is different from the classic inheritance models of the C++, Java, and C# languages. In the conventional inheritance model, classes inherit from classes. Classes are nothing more than specifications or blueprints used to create objects. Specifications can inherit the qualities of other specifications. Using inherited qualities and its own qualities, new objects can be instantiated. Unlike traditional inheritance, JavaScript has no concept of these specifications.

What is JavaScript Prototype Inheritance?

In JavaScript prototype inheritance, one object inherits from another object, instead of one specification inheriting from another specification. Even the new class keyword is kind of a misnomer because while it implies a specification, it is still one object inheriting from another object. So if the inheritance mechanism is the same, why change the syntax needed to perform the task already accomplished in earlier versions of JavaScript? Simply put, the syntax in earlier versions of JavaScript is too tedious and difficult to follow. Therefore, once the developer understands object-to-object inheritance, the second challenge is how to improve the syntax for prototype inheritance – enter ES2015 classes.

ES2015 Classes in JavaScript

The new ES2015 syntax provides a clearer syntax for identifying class structures, creating constructor functions, extending classes, calling the constructor and functions on the super class, as well as providing static functions. Additionally, the new ES2015 class construct improves the syntax for creating ES5 style getter/setter property descriptors, enabling developers to utilize these largely unknown capabilities in ES2015.

Class Definitions
The use of the term “class” in JavaScript has a controversial history. Strictly speaking, JavaScript does not have classes. Even the classes in ES2015 are not really classes in the traditional sense. The classes in ES2015 are just a cleaned up syntax for setting up prototype inheritance between objects. Nevertheless, because ES2015 uses the term “class” for objects created with a function constructor (yes, a function constructor is the ultimate end result of the class keyword), this blog post will use the term “class” to describe not only ES2015 classes, but also ES5 and earlier constructor functions.

In ES5 and earlier, constructor functions defined “classes” like this:

function MyClass() { }

var myClass = new MyClass();

ES2015 introduces a new syntax using the class keyword:

class MyClass {
  constructor() {  }
}
var myClass = new MyClass();

Click Here to Download the Code [typeof.js]

The constructor function is the same function as defined in ES5. The class keyword wrapped block is where properties on the prototype are defined (this will be shown later). The new keyword syntax for instantiating a new instance of the class remains unchanged from ES5.

With this new class keyword syntax comes a function object, which is what ES5 used. Observe the following output from the Node.js REPL environment. First, we define a new class and then the typeof operator lists the type of the class object.

> class MyClass { constructor() {} }
class MyClass { constructor() {} }
[Function: MyClass]
> typeof MyClass
'function'
>

ES2015 did not redefine the role and purpose of the constructor function; it simply cleaned up the syntax.

What are Constructors in JavaScript?

Constructors serve the same purpose as constructor functions in earlier versions of JavaScript. A constructor is a function that is executed when the new operator is used to instantiate a new instance of the class. Arguments can be passed into the constructor function to initialize object properties and perform other tasks.

In ES5, constructor functions looked like this:

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

The equivalent constructor function with ES2015 syntax looks like this:

// the name of the ES5 constructor
// function is name of the ES2015 class
class Person {

  // observe there is no "function" keyword
  // also, the word "constructor" is used, not "Person"
  constructor(firstName, lastName) {

    // this represents the new object being
    // created and initialized
    this.firstName = firstName;
    this.lastName = lastName;

  }
}

While the syntax is a little more verbose, it is also clearer, and makes it easier to add inherited properties.

To instantiate objects with either syntax, the code is the same.

var person = new Person("Bob", "Smith");

// outputs "Bob"
console.log(person.firstName);

// outputs "Smith"
console.log(person.lastName);

Click Here to Download the Code [constructors.js]

Extending Classes

Prior to ES2015, most developers who coded JavaScript did not understand how to setup an inheritance relationship between objects. A quick conversation with a C++, Java, or C# developer would quickly reveal the ease with which they could setup one class to inherit from another, and then instantiate an object from the subclass. Ask a typical JavaScript developer to demonstrate how to setup inheritance between two objects, and the result is usually a blank stare. The reason for this great difference is that configuring prototype inheritance is not straightforward, and the concept of prototype inheritance is foreign to most JavaScript developers. Here is some example code with comments to explain the process of configuring inheritance.

// Person constructor function
// when called with the "new" operator,
// a new Person object is created

function Person(firstName, lastName) {
  // the "new" operator sets the reference of
  // "this" to a new object
  this.firstName = firstName;
  this.lastName = lastName;
}

// this property referencing the function will
// be configured on person's prototype object,
// and will be inherited by students
Person.prototype.getFullName = function() {
  return this.firstName + " " + this.lastName;
};

// Student constructor function
// when called with the "new" operator,
// a new Student object is created

function Student(studentId, firstName, lastName) {
  // the "new" operator sets the reference of "this" to
  // a new object, the new object is then passed to the
  // Person constructor function through the use of call,
  // so the first name and last name properties can be set
  this._super.call(this, firstName, lastName);
  this.studentId = studentId;
}

// students will inherit from a new object
// which inherits from the parent
Student.prototype = Object.create(Person.prototype);

// set the constructor property back to the Student
// constructor function
Student.prototype.constructor = Student;

// "_super" is NOT part of ES5, its a convention
// defined by the developer
// set the "_super" to the Person constructor function
Student.prototype._super = Person;

// this will exist on the student's prototype object
Student.prototype.getStudentInfo = function() {
  return this.studentId + " " + this.lastName + ", " + this.firstName;
};

// instantiate a new Student object
var student = new Student(1, "Bob", "Smith");

// invoking function on parent prototype
// outputs "Bob Smith"
console.log(student.getFullName());

// invoking function on child prototype
// output "1 Smith, Bob"
console.log(student.getStudentInfo());

Click Here to Download the Code [es5_inheritance.js]

The above code is hard to follow, and it takes a lot of work just for one object to inherit from another while supporting constructor functions. Most JavaScript developers cannot create this code from memory, and many have never seen or considered anything like this when working with JavaScript.

To solve this problem and bring prototype inheritance into greater usage, ES2015 has introduced the extends keyword to the syntax of its new class structure. The following code demonstrates the same inheritance as the first code sample, but uses ES2015 syntax.

"use strict";

class Person {

  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  getFullName() {
    return this.firstName + " " + this.lastName;
  }

}

class Student extends Person {

  constructor(studentId, firstName, lastName) {
    super(firstName, lastName);
    this.studentId = studentId;
  }

  getStudentInfo() {
    return this.studentId + " " + this.lastName + ", " + this.firstName;
  }

}

var student = new Student(1, "Bob", "Smith");
console.log(student.getFullName());
console.log(student.getStudentInfo());

Click Here to Download the Code [es6_inheritance.js]

Clearly, the second approach easier to understand. The interesting result is that both code samples produce exactly the same object structure. Therefore, while the new syntax simply improves the inheritance code, it does not change the result.

Another way to explore how this works is to look at the ES5 inheritance code generated by TypeScript. TypeScript is a JavaScript pre-processor language, which enhances JavaScript through strong-type checking and transpiling ES2015 code to ES5 code. Transpiling (aka transcompiling) is the process of compiling the source code of one language into the source code of another language. Popular transpilers include CoffeeScript, SASS, and LessCSS, as well as TypeScript.

The _extends function in JavaScript

To support ES2015 class inheritance, TypeScript transpiles the ES2015 extends keyword functionality to a function named __extends, which executes the code needed to setup the inheritance. Here is the code for the __extends function:

var __extends = (this && this.__extends) || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};

The above code is a little hard to follow, so here is an expanded, documented version of it. Review the added source code comments to understand the purpose of each line of code. The __extends function works with any pair of parent and child objects.

// declare a variable to reference the extends function
var __extends;

if (this && this.__extends) {

	// the extends function is already defined within the context
	// of this code, so use the existing __extends function
	__extends = this.__extends;

} else {

Within the else block is the implementation of the __extends function. The function utilizes both the mixin pattern and prototype inheritance to construct the inheritance relationship between parent and child objects. The mixin pattern copies the properties from one object to another. The code below walks through the __extends function.

// the extends function is not already defined within the current
// context; therefore, define it
__extends = function (child, parent) {

  // mixin pattern for copying parent constructor function properties
  // as static properties to the child constructor function
  // properties on constructor function are commonly known as static
  // properties
  for (var parentPropertyName in parent) {

    // only copy properties specifically defined on the parent
    if (parent.hasOwnProperty(parentPropertyName)) {
      // for primitive types, this will copy the value,
      // for object types, this will copy the reference only
      child[parentPropertyName] = parent[parentPropertyName];
    }

  }

  // constructor function for the object that instantiated child objects
  // will inherit from
  // this function is unique within the context of each call to extend

  function __() {
    this.constructor = child;
  }

  if (parent === null) {

    // objects instantiated with the child constructor function will
    // inherit from an object that inherits from nothing, not even
    // the built-in JavaScript Object
    child.prototype = Object.create(parent);

  } else {

    // assign the prototype property of the parent constructor function
    // to the prototype property of the constructor function defined
    // above
    __.prototype = parent.prototype;

    // create the object that all instances of the child will inherit
    // from, and assign it to the prototype property of the child
    // constructor function
    child.prototype = new __();

  }

};

The following two lines of code confuse many developers:

// assign the prototype property of the parent constructor function
// to the prototype property of the constructor function defined
// above
__.prototype = parent.prototype;

// create the object that all instances of the child will inherit
// from, and assign it to the prototype property of the child
// constructor function
child.prototype = new __();

Developers may think the code should be written like this this instead:

// this code does not yield the desired result
child.prototype = parent.prototype;

Mistakenly, developers reason that the child will now inherit from the parent constructor function’s prototype property. However, what really happens is that objects created with the parent’s constructor function, and objects created with the child’s constructor function, both inherit from the exact same prototype object. This is not desirable, because the child constructor function’s prototype property cannot be modified without also changing the parent constructor function’s prototype property. This means that all changes made to the child will also be made to the parent. This is not true inheritance.

Inheritance Structure

When a new object is instantiated with the new operator and either the Parent or Child Constructor Functions, the resulting objects will inherit from the same prototype object (PPO). The instantiated parent and child objects are really siblings with the PPO as their parent. The child does not inherit from the parent.

Therefore, the purpose of this code is to establish the following inheritance structure.

__.prototype = parent.prototype;
child.prototype = new __();

Correct Inheritance Structure

With this new structure, new child objects will inherit from the CPO, which inherits from PPO. New properties can be added to the CPO, which will not affect the PPO. New parent objects will inherit from the PPO, and are thus unaffected by changes to the CPO. Changes to the PPO will be inherited by object created with both the Parent and Child Constructor Functions. With this new structure, child objects inherit from the parent.
Finally, the closing curly brace for the original if block.

}

Click Here to Download the Code [ts_extends.js]

ES2015 syntax for extending classes is much easier to understand. There are two new keywords: extends and super. The extends keyword sets up the prototype inheritance relationship between the parent and child classes. The super keyword invokes the constructor on the parent (aka super) class. Invoking the super function is required even if the parent object does no configuration. The invoking of the super constructor is what actually created the new this object to be used in the child class (aka subclass).

class Person {

  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  getFullName() {
    return this.firstName + " " + this.lastName;
  }

}

// 'extends' keyword sets up the prototype inheritance relationship
// with the super object
// 'extends' performs the same basic operation as the earlier
// '__extends' function

class Student extends Person {

  constructor(studentId, firstName, lastName) {
    // 'super' must be the first function called in the constructor
    // the 'super' invokes the constructor of the super class
    // the value of 'this' is not defined until 'super' is called
    super(firstName, lastName);

    // if this line is executed before the call to 'super' above
    // an error will occur saying that 'this' is not defined
    this.studentId = studentId;
  }

  getStudentInfo() {
    return this.studentId + " " + this.lastName + ", " + this.firstName;
  }

}

var student = new Student(1, "Bob", "Smith");

Click Here to Download the Code [extends.js]

JavaScript objects only allow single inheritance, and there is no built-in support for interfaces (TypeScript offers interfaces).

By adopting and formalizing many current JavaScript object design patterns, ES2015 classes greatly improves the syntax to define object inheritance, getter/setter properties. While ES2015 classes do not change the nature of prototype inheritance, it does make prototype inheritance more accessible to JavaScript developers. I hope that this new syntax will allow new and old JavaScript developers alike to expand their understanding and use of prototype inheritance in JavaScript.

JavaScript ES2015 Classes and Properties (Part 2 of 2)


Author: Eric Greene, one of Accelebrate’s instructors.

Accelebrate offers private AngularJS training and JavaScript training for groups and instructor-led online JavaScript classes for individuals.

Categories: JavaScript Articles
Tags: , , , ,

4 Responses to "JavaScript ES2015 Classes and Prototype Inheritance (Part 1 of 2)"

Leave a Reply

Your email address will not be published. Required fields are marked *

Your email address will not be published. Required fields are marked *

*



You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Please contact us for GSA pricing.
Contract #GS-35F-0307T

Please see our complete list of
Microsoft Official Courses