Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Lindsey Redinger
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Lindsey Redinger

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

By
Published in Comments (4)

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.

Want to use code from this post? Check out the license.

Reader Comments

1 Comments
Object.setPrototypeOf(SubClass, BaseClass);
// SubClass.__proto__ = BaseClass;

Use this code, SubClass.hasOwnProperty('isBaseClass') return false, but typeof SubClass.isBaseClass return function string.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel