January 27, 2016 in JavaScript Articles
Written by Eric Greene
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.
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.
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.
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.
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.
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 __();
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 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.
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
Email to [email protected]
925B Peachtree Street, NE
PMB 378
Atlanta, GA 30309-3918
USA
Please see our complete list of
Microsoft Official Courses
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
Baton 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.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.