Your privacy matters: This site uses cookies to analyze site usage and provide social media features. Learn More.

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

January 27, 2016 in JavaScript Articles

Written by Eric Greene


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.


Written by Eric Greene

Eric Greene

Eric is a professional software developer specializing in HTML, CSS, and JavaScript technologies. He has been developing software and delivering training classes for nearly 19 years. He holds the MCSD Certification for ASP.Net Web Applications, and is a Microsoft Certified Trainer.



Contact Us:

Accelebrate’s training classes are available for private groups of 3 or more people at your site or online anywhere worldwide.

Don't settle for a "one size fits all" public class! Have Accelebrate deliver exactly the training you want, privately at your site or online, for less than the cost of a public class.

For pricing and to learn more, please contact us.

Contact Us Train For Us

Toll-free in US/Canada:
877 849 1850
International:
+1 678 648 3113

Toll-free in US/Canada:
866 566 1228
International:
+1 404 420 2491

925B Peachtree Street, NE
PMB 378
Atlanta, GA 30309-3918
USA

Subscribe to our Newsletter:

Never miss the latest news and information from Accelebrate:

Microsoft Gold Partner

Please see our complete list of
Microsoft Official Courses

Recent Training Locations

Alabama

Huntsville

Montgomery

Birmingham

Alaska

Anchorage

Arizona

Phoenix

Tucson

Arkansas

Fayetteville

Little Rock

California

San Francisco

Oakland

San Jose

Orange County

Los Angeles

Sacramento

San Diego

Colorado

Denver

Boulder

Colorado Springs

Connecticut

Hartford

DC

Washington

Florida

Fort Lauderdale

Miami

Jacksonville

Orlando

Saint Petersburg

Tampa

Georgia

Atlanta

Augusta

Savannah

Idaho

Boise

Illinois

Chicago

Indiana

Indianapolis

Iowa

Ceder Rapids

Des Moines

Kansas

Wichita

Kentucky

Lexington

Louisville

Louisiana

Banton Rouge

New Orleans

Maine

Portland

Maryland

Annapolis

Baltimore

Hagerstown

Frederick

Massachusetts

Springfield

Boston

Cambridge

Michigan

Ann Arbor

Detroit

Grand Rapids

Minnesota

Saint Paul

Minneapolis

Mississippi

Jackson

Missouri

Kansas City

St. Louis

Nebraska

Lincoln

Omaha

Nevada

Reno

Las Vegas

New Jersey

Princeton

New Mexico

Albuquerque

New York

Buffalo

Albany

White Plains

New York City

North Carolina

Charlotte

Durham

Raleigh

Ohio

Canton

Akron

Cincinnati

Cleveland

Columbus

Dayton

Oklahoma

Tulsa

Oklahoma City

Oregon

Portland

Pennsylvania

Pittsburgh

Philadelphia

Rhode Island

Providence

South Carolina

Columbia

Charleston

Spartanburg

Greenville

Tennessee

Memphis

Nashville

Knoxville

Texas

Dallas

El Paso

Houston

San Antonio

Austin

Utah

Salt Lake City

Virginia

Richmond

Alexandria

Arlington

Washington

Tacoma

Seattle

West Virginia

Charleston

Wisconsin

Madison

Milwaukee

Alberta

Edmonton

Calgary

British Columbia

Vancouver

Nova Scotia

Halifax

Ontario

Ottawa

Toronto

Quebec

Montreal

Puerto Rico

San Juan

© 2013-2019 Accelebrate, Inc. All Rights Reserved. All trademarks are owned by their respective owners.