Inherit ES6/TS class from non-class Inherit ES6/TS class from non-class typescript typescript

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 anyclass 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 = evalnew 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.


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();    }}


I'm not sure I answer your question but I was very interested how to extend a JS function by a TypeScript class so I tried following:

fn.js (note the .js extension!)

function Fn() {    console.log("2");}

c.ts

declare class Fn {}class C extends Fn {    constructor() {        console.log("1");        super();        console.log("3");    }}let c = new C();c.sayHello();

Then I ran:

$ tsc --target es5 c.ts | cat fn.js c.js | node  # or es6

and the output is:

123Hello!

Note, that this is not code for production use but rather a workaround for cases when you don't have time to convert some old JS file to TypeScript.

If I was in OP situation, I would try to convert Fn to a class because it makes code easier for others in a team.