Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inherit ES6/TS class from non-class

Given the class is extended from non-class (including, but not limited to, function),

function Fn() {} 

class Class extends Fn {
    constructor() {
        super();
    }
}

what are the the consequences? What do the specs say on that?

It looks like the current implementations of Babel, Google V8 and Mozilla Spidermonkey are ok with that, and TypeScript throws

Type '() => void' is not a constructor function type

If this is a valid ES2015 code, what's the proper way to handle it in TypeScript?

like image 264
Estus Flask Avatar asked Jan 03 '16 12:01

Estus Flask


People also ask

How do you inherit from a class A into a class B in ES6?

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.

Does ES6 support inheritance?

The ES6 JavaScript supports Object-Oriented programming components such as Object, Class and Methods. Further in Classes we can implement inheritance to make child inherits all methods of Parent Class. This can be done using the extends and super keywords. We use the extends keyword to implement the inheritance in ES6.

Does TypeScript support inheritance?

TypeScript supports single inheritance and multilevel inheritance. We can not implement hybrid and multiple inheritances using TypeScript. The inheritance uses class-based inheritance and it can be implemented using extends keywords in typescript.


2 Answers

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.

like image 65
Herrington Darkholme Avatar answered Nov 17 '22 14:11

Herrington Darkholme


what are the the consequences? What do the specs say on that?

It is the same as extending a "EcmaScript 5" class. Your declare a constructor function and no prototype at all. You can extend it without any problem.

But for TypeScript, there is a big difference between function Fn() {} and class Fn {}. The both are not of the same type.

The first one is a just a function returning nothing (and TypeScript show it with the () => void). The second one is a constructor function. TypeScript refuse to do an extends on a non constructor function.

If one day javascript refuse to do that, it will break many javascript codes. Because at the moment, function Fn() {} is the most used way to declare a class in pure javascript. But from the TypeScript point of view, this is not "type safe".

I think the only way for TypeScript is to use a class :

class Fn {} 

class Class extends Fn {
    constructor() {
        super();
    }
}
like image 4
Magus Avatar answered Nov 17 '22 14:11

Magus