Inherit ES6/TS class from non-class - javascript

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?

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.

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:
1
2
3
Hello!
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.

This is indirectly related to TypeScript interface to describe class
Although the typeof a class is function, there is no Class (nor Interface) in Typescript which is the super class of all classes.
functions however are all interfaced by Function
I guess to be consistent with Javascript, Function should be probably be a class, all functions should be of that class and all classes should extend it...

Related

How can I convert an object constructor from javascript to work in typescript?

I am learning TypeScript by converting a vanilla JS toto list project to React + TypeScript.
In my old code, I had an object constructor Project that worked just fine:
function Project(title) {
this.title = title;
this.tasks = {};
}
As I try to implement this same constructor in TypeScript it seems like TS doesn't like my use of the word this. It underlines both this statements in red and hovering over it gives me the error:
'this' implicitly has type 'any' because it does not have a type annotation.
I tried adding types to it like so:
this.title: string = title;
title: string = title;
Both give me the error:
'string' only refers to a type, but is being used as a value here.
My main question is how would I write this same object constructor to work in TypeScript? What am I doing wrong with this? I have tried googling the problem but I don't see anything about this issue specifically on stack overflow or the typescript docs.
It looks like you can still make classes in typescript but I have tried to avoid them since 1) They aren't "real" in javascript, just syntactical sugar, and 2) I'd like to learn how to implement object constructors in TypeScript out of sheer intellectual curiosity, if possible.
https://www.typescriptlang.org/docs/handbook/2/classes.html#this-parameters
In a method or function definition, an initial parameter named this has special meaning in TypeScript. These parameters are erased during compilation:
// TypeScript input with 'this' parameter
function fn(this: SomeType, x: number) {
/* ... */
}
use class to solve the problem:
class Project {
tasks: Record<string, unknown> = {}
constructor(public title: string) {
}
}

abstract static method in TypeScript

I'm looking for a way to implement an abstract static method in TypeScript.
Here an example:
abstract class A {
abstract static b (): any // error
}
class B extends A {
static b () {}
}
This is allowed with public, private and protected methods but not with a static method. TypeScript returns the following error:
'static' modifier cannot be used with 'abstract' modifier.ts(1243)
Is there any workaround?
I think the reasoning is generally as follows:
Abstract methods are placeholders that a concrete subclass is supposed to implement.
The abstract base class will have a concrete method which uses this abstract placeholder, and thus requires it to be implemented; without this, abstract methods make little sense at all. E.g.:
abstract class Foo {
bar() {
return this.baz() + 1;
}
abstract baz(): int;
}
This can only be called on a concrete instance, e.g.:
function (foo: Foo) {
let val = foo.bar();
...
}
The result will be different here depending on what concrete subclass of Foo you get, but it's all guaranteed to work.
Now, what would this look like with static methods?
abstract class Foo {
static bar() {
return this.baz() + 1;
}
abstract static baz(): int;
}
function (F: typeof Foo) {
let val = F.bar();
...
}
This looks somewhat logical and like it should work, shouldn't it? But:
If you write this, there's basically no difference to passing instances of classes, except for the awkward typeof typing. So, why?
If you never instantiate the class, what's the point of a class?
If your static method does instantiate an object, if it acts as an alternative constructor—which is entirely legitimate—then calling F.bar() to get a new instance, you'd expect to get an instance of F, but you might be getting an instance of some subclass of it. And that's arguably not desired.
To rephrase the chain of argument here:
If you're going to use static methods at all, they should act as alternative constructors and return an instance, otherwise they have little business being part of the class.
Alternative constructors should return an instance of the class that you called them on. Calling F.bar() and not getting an instance of exactly F is… weird?
If you're going to call static alternative constructors on classes, you're going to want to call them on specific classes and not variables as shown above, because otherwise you really won't know what you're getting (see point above).
Therefore, there's no real use case for an abstract static method, either as direct alternative constructor nor as helper function for one, since that would lead to a violation of one of the above points one way or another.
As far as I see, this is pretty much the classical thinking that lead to abstract static methods not being a thing. Certain technical limitations in Java may have contributed to that and/or lead to said technical limitations in the first place.
In Javascript one can argue that this makes less sense, since even classes are objects and it would be entirely feasible to use them as described above, and in certain situations it may even make sense. But here we are.

How and why would I write a class that extends null?

JavaScript's class syntax, added in ES6, apparently makes it legal to extend null:
class foo extends null {}
Some Googling reveals that it was suggested on ES Discuss that such declarations be made an error; however, other commenters argued for them to be left legal on the basis that
someone might want to create a class that has a {__proto__: null} prototype
and that side of the argument ultimately prevailed.
I can't make much sense of this hypothetical use case. For one thing, while the declaration of such a class is legal, it seems that instantiating a class declared in this way isn't. Trying to instantiate the class foo from above in Node.js or Chrome gives me the wonderfully daft error
TypeError: function is not a function
while doing the same in Firefox gives me
TypeError: function () {
} is not a constructor
It doesn't help to define a constructor on the class, as shown in MDN's current example of the feature; if I try to instantiate this class:
class bar extends null {
constructor(){}
}
then Chrome/Node tell me:
ReferenceError: this is not defined
and Firefox tells me:
ReferenceError: |this| used uninitialized in bar class constructor
What is all this madness? Why are these null-extending classes not instantiable? And given that they're not instantiable, why was the possibility of creating them deliberately left in the spec, and why did some MDN author think that it was noteworthy enough to document? What possible use case is there for this feature?
EDIT (2021): TC39, which specifies JavaScript still hasn't resolved exactly how this is supposed to work. That needs to happen before browsers can consistently implement it. You can follow the latest efforts here.
Original answer:
Instantiating such classes is meant to work; Chrome and Firefox just have bugs. Here's Chrome's, here's Firefox's. It works fine in Safari (at least on master).
There used to be a bug in the spec which made them impossible to instantiate, but it's been fixed for a while. (There's still a related one, but that's not what you're seeing.)
The use case is roughly the same as that of Object.create(null). Sometimes you want something which doesn't inherit from Object.prototype.
To answer the second part:
I can't make much sense of this hypothetical use case.
That way, your object won't have Object.prototype in its prototype chain.
class Hash extends null {}
var o = {};
var hash = new Hash;
o["foo"] = 5;
hash["foo"] = 5;
// both are used as hash maps (or use `Map`).
hash["toString"]; // undefined
o["toString"]; // function
As we know, undefined in fact is not a function. In this case we can create objects without fearing a call on a field that shouldn't be there.
This is a common pattern through Object.create(null) and is common in a lot of code bases including big ones like NodeJS.
It is in fact possible to construct a class that extends null, as such
class bar extends null{
constructor(){
super() //required
}
}
//super is not a function
//change the super class (for static methods only)
Object.setPrototypeOf(bar, class{})
let instance = new bar()
//instances still have null prototype
console.log(instance.toString) //undefined

Typescript: why this code throws an exception while it is a 100% valid javascript code

It is claimed that TypeScript is a superset of Javascript.
Here's a question on Stack about this.
Here's a quote from spec:
TypeScript is a syntactic sugar for JavaScript. TypeScript syntax is a
superset of ECMAScript 2015 (ES2015) syntax. Every JavaScript program
is also a TypeScript program.
So my understanding is that any stand-alone javascript file can be treated as a valid typescript code, i.e. compiled (may be with some additional flags) by tsc compiler.
But here's an example of js code:
class ClassA {}
ClassA.prototype.ping = () => {console.log('PING')}
That is valid javascript but if you'll try to compile it with typescript, you'll get:
error TS2339: Property 'ping' does not exist on type 'ClassA'
One can declare interface which ClassA can implement, also, it's highly untypical to write code like this (combine class and prototype syntaxes) but nevertheless - this looks like an example of valid js code which raises an error while compiling with tsc.
So the question is - how this does not contradict to the quote from spec?
TypeScript is a syntactic superset of JavaScript that doesn't change JavaScript's runtime behavior. So any expression or statement or declaration you write in JavaScript is syntactically legal TypeScript.
This doesn't mean that all JS code is considered to be warning-free TypeScript. After all, a major goal of TypeScript is to identify bad constructs like
var s = "hello world" * 423;
var t = "qzz".subtr(2);
var u = [1, 2, 3] + 5;
var w = window.navgtator;
These are all "valid" JavaScript expressions, they just happen to have undesirable runtime behavior that people don't want to actually do.
As usual in TypeScript, you can tell the type system about extra information
class ClassA {}
// Declare additional method
interface ClassA { ping(): void; }
// OK
ClassA.prototype.ping = () => {console.log('PING')}

What is the purpose of an abstract METHOD in javascript?

// A convenient function that can be used for any abstract method
function abstractmethod() { throw new Error("abstract method"); }
// The AbstractSet class defines a single abstract method, contains().
function AbstractSet() { throw new Error("Can't instantiate abstract classes");}
AbstractSet.prototype.contains = abstractmethod;
From "Javascript: The Definitive Guide - 9.7.4 Class Hierarchies and Abstract Classes"
I understand the utility of abstract classes in JavaScript. What I don't understand is the necessity or use of setting abstract methods that only throw an error. You can't create an instance of that class, so only instances of the subclasses will exist. They'll each have their own definition for these methods, so why establish an inheritance to a method that just throws a generic error?
Thank you in advance for your guiding response.
I assume that the purpose of this is that it's not a generic error - it's an error that informs you where you went wrong (i.e. you failed to override the method in a subclass). If you didn't define that method, you would get an error saying "Undefined is not a function" (or something similar) and you'd have to spend time hunting around your code to understand why - this way it fails in a more verbose and useful manner.
The other reason, I'd assume, is to indicate the class interface to downstream developers implementing subclasses. Javascript doesn't have any kind of formal interface declaration, so it's helpful to be able to inspect the abstract class and see what methods I'm expected to implement.

Categories

Resources