How to Inherit Old-Style Class from Ecmascript 6 Class in JavaScript

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, newing 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



Leave a reply



Submit