Is it possible to inherit old-style class from ECMAScript 6 class in JavaScript?
There's not really a way out once you've opted in to class
syntax.
The problem is that inheritance in ES6 is done by late-initialising the this
keyword with the return value from super()
, which is a constructor call like with new
. The old idiom of "applying" (.call()
) the parent constructor on the currently "uninitialised" child instance does work no more.
What you can still do is to resort to "parasitic inheritance" - you'll have to explicitly construct the instance, extend it, and return
it:
function MyDerived() {
var derived = new MyClass('MyDerived');
… // set up properties
return derived;
}
When you do this with new MyClass
, you won't get the prototype set up correctly however. For that, you will need to use the new ES6 Reflect.construct
function, which takes the child class as an optional third parameter:
function MyDerived() {
var derived = Reflect.construct(MyClass, ['MyDerived'], new.target||MyDerived);
… // set up properties
return derived;
}
This has the additional benefit that MyDerived
doesn't need to be called with new
any more (as long as you supply MyDerived
when new.target
is empty).
How to inherit from a class in javascript?
I have changed how I do this now, I try to avoid using constructor functions and their prototype
property, but my old answer from 2010 is still at the bottom. I now prefer Object.create()
. Object.create
is available in all modern browsers.
I should note that Object.create
is usually much slower than using new
with a function constructor.
//The prototype is just an object when you use `Object.create()`
var Base = {};
//This is how you create an instance:
var baseInstance = Object.create(Base);
//If you want to inherit from "Base":
var subInstance = Object.create(Object.create(Base));
//Detect if subInstance is an instance of Base:
console.log(Base.isPrototypeOf(subInstance)); //True
jsfiddle
One of the big benefits of using Object.create is being able to pass in a defineProperties argument, which gives you significant control over how properties on the class can be accessed and enumerated over, and I also use functions to create instances, these serve as constructors in a way, as you can do initialization at the end instead of just returning the instance.
var Base = {};
function createBase() {
return Object.create(Base, {
doSomething: {
value: function () {
console.log("Doing something");
},
},
});
}
var Sub = createBase();
function createSub() {
return Object.create(Sub, {
doSomethingElse: {
value: function () {
console.log("Doing something else");
},
},
});
}
var subInstance = createSub();
subInstance.doSomething(); //Logs "Doing something"
subInstance.doSomethingElse(); //Logs "Doing something else"
console.log(Base.isPrototypeOf(subInstance)); //Logs "true"
console.log(Sub.isPrototypeOf(subInstance)); //Logs "true
jsfiddle
This is my original answer from 2010:
function Base ( ) {
this.color = "blue";
}
function Sub ( ) {
}
Sub.prototype = new Base( );
Sub.prototype.showColor = function ( ) {
console.log( this.color );
}
var instance = new Sub ( );
instance.showColor( ); //"blue"
proto inheritance from ES6 class
Since you're running this all on an environment that actually supports real ES6 classes, you may be able to achieve what you're looking for. What you'll need to do is to change your subclass logic to be
var ProtoChildFromClassParent = function(a) {
const _this = Reflect.construct(ClassParent, [a], new.target);
return _this;
}
Object.setPrototypeOf(ProtoChildFromClassParent, ClassParent);
Object.setPrototypeOf(ProtoChildFromClassParent.prototype, ClassParent.prototype);
This is predicated on Reflect.construct
being available, so it will not work on an older ES5 environment, but then neither would ES6 class syntax either. It's also important that new.target
be available. As long as both are available, this is very close to replicating the behavior you'd get from using actual class syntax. That said, immediately the question would be why you're not just doing
class ProtoChildFromClassParent extends ClassParent {}
so whether this is useful or not really depends on what's stopping you from doing that to begin with.
ES6 Class Multiple inheritance
An object can only have one prototype. Inheriting from two classes can be done by creating a parent object as a combination of two parent prototypes.
The syntax for subclassing makes it possible to do that in the declaration, since the right-hand side of the extends
clause can be any expression. Thus, you can write a function that combines prototypes according to whatever criteria you like, and call that function in the class declaration.
How do I create a new class which both of my classes will inherit methods and properties from?
You can use the extends
keyword for inheritance in ES6 classes:
class GameObject {
constructor(sprite, positionX, positionY, speed) {
this.sprite = sprite;
this.positionX = positionX;
this.positionY = positionY;
this.speed = speed;
}
getCenterPoint() {
return new Point(this.positionX + 16, this.positionY + 16);
}
}
class Enemy extends GameObject {
constructor(...props) {
super(...props);
this.direction = Math.floor(Math.random() * 7) + 1;
this.direction *= Math.floor(Math.random() * 2) == 1 ? 1 : -1;
this.active = false;
}
}
class Player extends GameObject {
constructor(...props) {
super(...props);
this.animationFrame = true;
}
}
Inherit ES6/TS class from non-class
TypeScript Part
Up to now, the spec says a extends
claus must be followed by a TypeReference. And a TypeReference must be in the form of A.B.C<TypeArgument>
, like MyModule.MyContainer<MyItem>
. So, syntactically your code is right. But it is not the typing case.
The spec says the BaseClass
must be a valid typescript class. However, the spec is outdated, as said here. Now TypeScript allows expressions in extends clause, as long as expressions are computed to a constructor function. The definition is, well, implementation based. You can see it here. Simply put, a expression can be counted as constructor if it implements new() {}
interface.
ES2015 Part
So, your problem is plain function is not recognized as constructor in TypeScript, which is arguable because ES2015 spec only requires the object has a [[construct]]
internal method. While user-defined function object does have it.
ES2015 requires BaseClass
is a constructor at runtime. An object isConstructor
if it has [[construct]]
internal methd. The spec says [[construct]]
is an internal method for Function Object. User functions are instances of Function Objects
, so naturally they are constructor. But builtin function and arrow function can have no [[construct]]
.
For example, the following code will throw runtime TypeError
because parseInt
is a builtin function and does not have [[construct]]
new parseInt()
// TypeError: parseInt is not a constructor
And from ECMAScript
Arrow functions are like built-in functions in that both lack .prototype and any [[Construct]] internal method. So new (() => {}) throws a TypeError but otherwise arrows are like functions:
As a rule of thumb, any function without prototype
is not new
-able.
Work Around
In short, not every function is constructor, TypeScript captures this by requiring new() {}
. However, user-defined function is constructor.
To work around this, the easiest way is declare Fn
as a variable, and cast it into constructor.
interface FnType {}
var Fn: {new(): FnType} = (function() {}) as any
class B extends Fn {}
Reasoning the incompatiblity
DISCALIMER: I'm not a TypeScript core contributor, but just a TS fan who has several side project related to TS. So this section is my personal guess.
TypeScript is a project originated in 2012, when ES2015 was still looming in dim dark. TypeScript didn't have a good reference for class semantics.
Back then, the main goal of TypeScript was to keep compatible with ES3/5. So, new
ing a function is legal in TypeScript, because it is also legal in ES3/5. At the same time, TypeScript also aims to capture programming errors. extends
a function might be an error because the function might not be a sensible constructor (say, a function solely for side effect). extends
did not even exist in ES3/5! So TypeScript could freely define its own usage of extends
, making extends
must pair with class
variable. This made TypeScript more TypeSafe, while being compatible with JavaScript.
Now, ES2015 spec is finalized. JavaScript also has a extends
keyword! Then incompatibility comes. There are efforts to resolve incompatibility. However, still problems exist. () => void
or Function
type should not be extendsable, as stated above due to builtin function. The following code will break
var a: (x: string) => void = eval
new a('booom')
On the other hand, if a ConstructorInterface
was introduced into TypeScript and every function literal implemented it, then backward incompatibility would emerge. The following code compiles now but not when ConstructorInterface
was introduced
var a = function (s) {}
a = parseInt // compile error because parseInt is not assignable to constructor
Of course, TS team can have a solution that balancing these two options. But this is not a high priority. Also, if salsa, the codename for TS powered JavaScript, is fully implemented. This problem will be solved naturally.
Function.prototype.call on class to make dynamic inheritance
Class constructors in JavaScript can only be called with new
, Reflect.construct
orsuper
in a class extension. This tightens up and standardizes alternatives construction techniques using ordinary function
objects.
Although class
objects are functions and do inherit a call
method, attempting to use className.call(...)
will generate an error similar to
TypeError: class constructors must be invoked with 'new'
First answer: as a result of the above you will not be able to call the constructor of a class using `Function.prototype.call'.
As mentioned in comments, extending a base class is an alternative to creating a constructor function, but when written as a class declaration doesn't provide dynamic inheritance.
However this dosn't prevent you dynamically extending a class expression written inside a factory function. As an example:
class Person {
constructor(name, test) {
this.name = name;
this.test = test;
}
}
// factory function:
function policeExtend(baseClass) {
return class Police extends baseClass {
constructor(name, badge) {
super(name, "'just testing'");
this.badge = badge;
}
};
}
const PolicePerson = policeExtend(Person);
let officer = new PolicePerson("chief", 100);
console.log( officer.name, officer.badge, officer.test)
Extending ES2015 through prototype inheritance
ES6 created a clearer distinction between [[Call]]
and [[Construct]]
operations. ES6 classes can only be constructed using a [[Construct]]
operation, but .call
/.apply
are [[Call]]
operations. To use [[Construct]]
, you'll need to use Reflect.construct()
to create the new instance. e.g.
constructor = function () {
return Reflect.construct(Object.getPrototypeOf(constructor), arguments);
};
or
constructor = function () {
return Reflect.construct(currentClass, arguments);
};
I'll also note however that this also ignores another feature new in ES6, which is new.target
. You'll also want to preserve that, by doing
constructor = function () {
return Reflect.construct(currentClass, arguments, new.target);
};
to ensure that the things behave the same way as class syntax would.
Also note that you must not use this
inside this function. It is the return value of Reflect.construct
that is the new instance, so if you wanted to work with it in your own custom constructor, you'll want to do
constructor = function () {
const _this = Reflect.construct(currentClass, arguments, new.target);
_this.foo = "foo";
return _this;
};
however, if you think about it, this is essentially all the same as doing
constructor = class extends currentClass {}
Related Topics
Finding Longest String in Array
How to Redefine a JavaScript Class's Method
Can You Force Vue.Js to Reload/Re-Render
How to Add Predefined Length to Audio Recorded from Mediarecorder in Chrome
How to Load a JavaScript File Dynamically
Es6: Conditional & Dynamic Import Statements
Jqgrid - Maintain Check Box Selection State - Page Refresh/Redirect/Reload
Mobile Safari: JavaScript Focus() Method on Inputfield Only Works with Click
Retrieving Binary File Content Using JavaScript, Base64 Encode It and Reverse-Decode It Using Python
Method for Streaming Data from Browser to Server via Http
How to Calculate 3D Rotation on X and Y Axis from a 4X4 Matrix
How to Get a JavaScript Object Property Name That Starts with a Number
Add Event Handler to HTML Element Using JavaScript
Which Browsers Support Import and Export Syntax for Ecmascript 6
About Closure, Lexicalenvironment and Gc