Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at the New York ColdFusion User Group (Jul. 2008) with: Clark Valberg and Simon Free and Dan Wilson
Ben Nadel at the New York ColdFusion User Group (Jul. 2008) with: Clark Valberg@clarkvalberg ) , Simon Free@simonfree ) , and Dan Wilson@DanWilson )

Static Methods Are Inherited When Using ES6 Extends Syntax In JavaScript And Node.js

By Ben Nadel on

When I think about "classes" in ES6, I have historically thought of them (and discussed them) as nothing more than "syntactic sugar" on top of the core prototypal inheritance mechanism. But, it turns out, that's not entirely true. Or, at least, my mental model was insufficient. When using the "extends" syntax in ES6, sub-classes inherit both the instance methods (prototype) and the static methods (constructor properties) of the base class. When using ES5 prototypal inheritance directly, there is no automatic inheriting of static methods. And, I'd go so far as to say that I've never seen such behavior in the wild.


 
 
 

 
 
 
 
 

To see this in action, let's use ES6 class syntax to create a SubClass that extends a BaseClass that includes static methods. Then, let's try to use the static methods inherited from the BaseClass:

  • // Require the core node modules.
  • var chalk = require( "chalk" );
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • class BaseClass {
  •  
  • constructor() {
  • // ....
  • }
  •  
  • // ---
  • // STATIC METHODS.
  • // ---
  •  
  • static isBaseClass( value ) {
  •  
  • return( value instanceof BaseClass );
  •  
  • }
  •  
  • }
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • class SubClass extends BaseClass {
  •  
  • constructor() {
  • super();
  • }
  •  
  • // ---
  • // STATIC METHODS.
  • // ---
  •  
  • static isSubClass( value ) {
  •  
  • return( value instanceof SubClass );
  •  
  • }
  •  
  • }
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • console.log( chalk.red.bold( "SubClass.isBaseClass:" ), SubClass.isBaseClass );
  • console.log( chalk.red.bold( "SubClass.isSubClass:" ), SubClass.isSubClass );
  •  
  • var b = new BaseClass();
  • var s = new SubClass();
  •  
  • console.log( chalk.cyan( "SubClass.isBaseClass( b ):" ), SubClass.isBaseClass( b ) );
  • console.log( chalk.cyan( "SubClass.isSubClass( b ):" ), SubClass.isSubClass( b ) );
  •  
  • console.log( chalk.cyan( "SubClass.isBaseClass( s ):" ), SubClass.isBaseClass( s ) );
  • console.log( chalk.cyan( "SubClass.isSubClass( s ):" ), SubClass.isSubClass( s ) );

As you can see, the BaseClass has a static method isBaseClass() that simply checks the type of constructor the given value is using. Our SubClass then extends BaseClass, which gives it access to the BaseClass static methods. So, when we run this code through Node.js, we get the following terminal output:


 
 
 

 
 ES6 class extends syntax inherits static methods. 
 
 
 

As you can see, our SubClass has access to the isBaseClass() static method inherited from the BaseClass.

In ES5, most of the inheritance code that I've seen (including Node.js' util.inherits() method) does not affect the inheritance of the constructor function itself; but, rather, only the inheritance chain of the constructor function's prototype. As such, if we wanted to retrofit our ES5 code to work more like the ES6 code, we'd have to explicitly copy the BaseClass static methods:

  • // Require the core node modules.
  • var chalk = require( "chalk" );
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • function BaseClass() {
  • // ....
  • };
  •  
  • BaseClass.prototype = {
  • // No instance methods...
  • };
  •  
  • BaseClass.isBaseClass = function( value ) {
  •  
  • return( value instanceof BaseClass );
  •  
  • };
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • function SubClass() {
  • BaseClass.call( this );
  • }
  •  
  • // Inherit instance methods from base class.
  • SubClass.prototype = Object.create( BaseClass.prototype );
  •  
  • // Inherit static methods.
  • // --
  • // NOTE: In ES6, the static methods are inherited automatically; however, in ES5,
  • // extending the "class" prototype doesn't affect the inheritance chain of the
  • // constructor function itself. As such, we have to manually copy static methods from
  • // the base constructor into the sub constructor.
  • for ( var key in BaseClass ) {
  •  
  • if (
  • BaseClass.hasOwnProperty( key ) &&
  • ( typeof( BaseClass[ key ] ) === "function" )
  • ) {
  •  
  • SubClass[ key ] = BaseClass[ key ];
  •  
  • }
  •  
  • }
  •  
  • SubClass.isSubClass = function( value ) {
  •  
  • return( value instanceof SubClass );
  •  
  • };
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • console.log( chalk.red.bold( "SubClass.isBaseClass:" ), SubClass.isBaseClass );
  • console.log( chalk.red.bold( "SubClass.isSubClass:" ), SubClass.isSubClass );
  •  
  • var b = new BaseClass();
  • var s = new SubClass();
  •  
  • console.log( chalk.cyan( "SubClass.isBaseClass( b ):" ), SubClass.isBaseClass( b ) );
  • console.log( chalk.cyan( "SubClass.isSubClass( b ):" ), SubClass.isSubClass( b ) );
  •  
  • console.log( chalk.cyan( "SubClass.isBaseClass( s ):" ), SubClass.isBaseClass( s ) );
  • console.log( chalk.cyan( "SubClass.isSubClass( s ):" ), SubClass.isSubClass( s ) );

As you can see, we're using Object.create() to build the prototype chain. But, we're also manually copying static methods from the BaseClass to the SubClass. And, when we run this code through Node.js, we get the following terminal output:


 
 
 

 
 Mimic ES6 class functionality by manually copying static methods in ES5. 
 
 
 

As you can see, by manually copying the static methods from the BaseClass to the SubClass, we can create code that behaves more like the native "extends" feature of ES6.

Along with the new class syntax, ES6 also gives us more insight into the runtime prototype chain of our objects. And now that we've looked at how we might retrofit our ES5 code to behave more like our ES6 code, I wanted to try and inspect the ES6 code to learn more about the actual implementation:

  • // Require the core node modules.
  • var chalk = require( "chalk" );
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • class BaseClass {
  •  
  • static isBaseClass( value ) {
  •  
  • return( value instanceof BaseClass );
  •  
  • }
  •  
  • }
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • class SubClass extends BaseClass {
  •  
  • static isSubClass( value ) {
  •  
  • return( value instanceof SubClass );
  •  
  • }
  •  
  • }
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • console.log( chalk.red.bold( "Object.getPrototypeOf( SubClass ) === BaseClass" ) );
  • console.log( " =>", Object.getPrototypeOf( SubClass ) === BaseClass );
  •  
  • console.log( chalk.red.bold( "Object.getPrototypeOf( SubClass.prototype ) === BaseClass.prototype" ) );
  • console.log( " =>", Object.getPrototypeOf( SubClass.prototype ) === BaseClass.prototype );
  •  
  • console.log( chalk.red.bold( "SubClass.hasOwnProperty( \"isBaseClass\" )" ) );
  • console.log( " =>", SubClass.hasOwnProperty( "isBaseClass" ) );
  •  
  • console.log( chalk.red.bold( "SubClass.hasOwnProperty( \"isSubClass\" )" ) );
  • console.log( " =>", SubClass.hasOwnProperty( "isSubClass" ) );

Here, we're looking at the actual prototype chain of our SubClass; and, to see if the inherited static methods are available locally, or as part of the prototype chain. When we run this code through Node.js, we get the following terminal output:


 
 
 

 
 Inspecting the ES6 prototypes shows us that sub-class constructors inherit directly from base-class constructors. 
 
 
 

As you can see, the isBaseClass() static method is not a local copy in the SubClass constructor (which is what our ES5 code was doing). Instead, the isBaseClass() static method is made available through prototypal inheritance; in this case, because the BaseClass is the prototype of the SubClass.

This is really good stuff to know. ES6 class syntax isn't just syntactic sugar (depending on how you look at it) - it also provides some additional functionality that you don't normally get using ES5-based inheritance.



Looking For A New Job?

Ooops, there are no jobs. Post one now for only $29 and own this real estate!

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.