从非类继承ES6 / TS类

时间:2022-09-24 23:25:05

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

看起来Babel,Google V8和Mozilla Spidermonkey的当前实现都可以,并且TypeScript会抛出

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

类型'()=> void'不是构造函数类型

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

如果这是一个有效的ES2015代码,那么在TypeScript中处理它的正确方法是什么?

4 个解决方案

#1


12  

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.

到目前为止,规范规定扩展claus必须后跟TypeReference。 TypeReference必须采用A.B.C 的形式,如MyModule.MyContainer 。所以,从语法上讲,你的代码是正确的。但它不是打字机箱。

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.

规范说BaseClass必须是有效的typescript类。但是,规范已经过时,如此处所述。现在TypeScript允许在extends子句中使用表达式,只要表达式计算到构造函数。该定义是基于实现的。你可以在这里看到它。简单地说,如果表达式实现new(){}接口,则可以将其表示为构造函数。

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.

所以,你的问题是普通函数在TypeScript中不被认为是构造函数,这是有争议的,因为ES2015规范只要求对象有一个[[construct]]内部方法。用户定义的函数对象确实拥有它。

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]].

ES2015要求BaseClass是运行时的构造函数。一个对象是构造函数,如果它有[[construct]]内部的methd。规范说[[construct]]是Function Object的内部方法。用户函数是函数对象的实例,因此它们自然是构造函数。但内置函数和箭头函数可以没有[[construct]]。

For example, the following code will throw runtime TypeError because parseInt is a builtin function and does not have [[construct]]

例如,以下代码将抛出运行时TypeError,因为parseInt是内置函数,并且没有[[construct]]

new parseInt()
// TypeError: parseInt is not a constructor

And from ECMAScript

并从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:

箭头函数就像内置函数一样缺乏.prototype和任何[[Construct]]内部方法。所以new(()=> {})抛出一个TypeError,否则箭头就像函数:

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.

简而言之,并非每个函数都是构造函数,TypeScript通过要求new(){}来捕获它。但是,用户定义的函数是构造函数。

To work around this, the easiest way is declare Fn as a variable, and cast it into constructor.

要解决这个问题,最简单的方法是将Fn声明为变量,并将其强制转换为构造函数。

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.

DISCALIMER:我不是TypeScript核心贡献者,但只是一个TS粉丝,他有几个与TS相关的副项目。所以这部分是我个人的猜测。

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.

TypeScript是一个源自2012年的项目,当时ES2015在昏暗的黑暗中仍然隐约可见。 TypeScript没有类语义的良好引用。

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.

那时候,TypeScript的主要目标是与ES3 / 5保持兼容。因此,在TypeScript中新建函数是合法的,因为它在ES3 / 5中也是合法的。同时,TypeScript还旨在捕获编程错误。扩展函数可能是一个错误,因为函数可能不是一个合理的构造函数(比如,一个仅用于副作用的函数)。延伸甚至不存在于ES3 / 5中!因此TypeScript可以*定义自己对extends的使用,使得extends必须与类变量配对。这使TypeScript更加TypeSafe,同时与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

现在,ES2015规范已经完成。 JavaScript还有一个extends关键字!然后不兼容了。有努力解决不兼容问题。但是,仍然存在问题。 ()=> void或函数类型不应该是可扩展的,如上所述由于内置函数。以下代码将中断

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

另一方面,如果将一个ConstructorInterface引入TypeScript并且每个函数文本都实现了它,那么就会出现向后不兼容。以下代码现在编译,但在引入ConstructorInterface时不编译

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.

当然,TS团队可以有一个平衡这两个选项的解决方案。但这不是一个高优先级。此外,如果salsa(TS驱动的JavaScript的代号)完全实现。这个问题自然会得到解决。

#2


4  

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.

它与扩展“EcmaScript 5”类相同。你声明了一个构造函数,根本没有原型。您可以毫无问题地扩展它。

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

但对于TypeScript,函数Fn(){}和类Fn {}之间存在很大差异。两者的类型不同。

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.

第一个是一个只返回任何函数的函数(TypeScript用()=> void显示它。第二个是构造函数。 TypeScript拒绝对非构造函数执行扩展。

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".

如果有一天javascript拒绝这样做,它将破坏许多javascript代码。因为目前,函数Fn(){}是在纯javascript中声明类的最常用方法。但从TypeScript的角度来看,这不是“类型安全”。

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

我认为TypeScript的唯一方法是使用类:

class Fn {} 

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

#3


1  

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:

我不确定我是否回答了你的问题,但我对如何通过TypeScript类扩展JS函数非常感兴趣,所以我尝试了以下内容:

fn.js (note the .js extension!)

fn.js(注意.js扩展名!)

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

c.ts

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.

请注意,这不是用于生产用途的代码,而是在没有时间将某些旧JS文件转换为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.

如果我处于OP状态,我会尝试将Fn转换为类,因为它使团队中的其他人更容易编写代码。

#4


1  

This is indirectly related to TypeScript interface to describe class

这与描述类的TypeScript接口间接相关

Although the typeof a class is function, there is no Class (nor Interface) in Typescript which is the super class of all classes.

虽然类的类型是函数,但是在Typescript中没有Class(也没有Interface),它是所有类的超类。

functions however are all interfaced by Function

但是函数都是由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...

我想与Javascript一致,函数应该是一个类,所有函数都应该是该类,所有类都应该扩展它...

#1


12  

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.

到目前为止,规范规定扩展claus必须后跟TypeReference。 TypeReference必须采用A.B.C 的形式,如MyModule.MyContainer 。所以,从语法上讲,你的代码是正确的。但它不是打字机箱。

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.

规范说BaseClass必须是有效的typescript类。但是,规范已经过时,如此处所述。现在TypeScript允许在extends子句中使用表达式,只要表达式计算到构造函数。该定义是基于实现的。你可以在这里看到它。简单地说,如果表达式实现new(){}接口,则可以将其表示为构造函数。

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.

所以,你的问题是普通函数在TypeScript中不被认为是构造函数,这是有争议的,因为ES2015规范只要求对象有一个[[construct]]内部方法。用户定义的函数对象确实拥有它。

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]].

ES2015要求BaseClass是运行时的构造函数。一个对象是构造函数,如果它有[[construct]]内部的methd。规范说[[construct]]是Function Object的内部方法。用户函数是函数对象的实例,因此它们自然是构造函数。但内置函数和箭头函数可以没有[[construct]]。

For example, the following code will throw runtime TypeError because parseInt is a builtin function and does not have [[construct]]

例如,以下代码将抛出运行时TypeError,因为parseInt是内置函数,并且没有[[construct]]

new parseInt()
// TypeError: parseInt is not a constructor

And from ECMAScript

并从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:

箭头函数就像内置函数一样缺乏.prototype和任何[[Construct]]内部方法。所以new(()=> {})抛出一个TypeError,否则箭头就像函数:

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.

简而言之,并非每个函数都是构造函数,TypeScript通过要求new(){}来捕获它。但是,用户定义的函数是构造函数。

To work around this, the easiest way is declare Fn as a variable, and cast it into constructor.

要解决这个问题,最简单的方法是将Fn声明为变量,并将其强制转换为构造函数。

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.

DISCALIMER:我不是TypeScript核心贡献者,但只是一个TS粉丝,他有几个与TS相关的副项目。所以这部分是我个人的猜测。

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.

TypeScript是一个源自2012年的项目,当时ES2015在昏暗的黑暗中仍然隐约可见。 TypeScript没有类语义的良好引用。

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.

那时候,TypeScript的主要目标是与ES3 / 5保持兼容。因此,在TypeScript中新建函数是合法的,因为它在ES3 / 5中也是合法的。同时,TypeScript还旨在捕获编程错误。扩展函数可能是一个错误,因为函数可能不是一个合理的构造函数(比如,一个仅用于副作用的函数)。延伸甚至不存在于ES3 / 5中!因此TypeScript可以*定义自己对extends的使用,使得extends必须与类变量配对。这使TypeScript更加TypeSafe,同时与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

现在,ES2015规范已经完成。 JavaScript还有一个extends关键字!然后不兼容了。有努力解决不兼容问题。但是,仍然存在问题。 ()=> void或函数类型不应该是可扩展的,如上所述由于内置函数。以下代码将中断

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

另一方面,如果将一个ConstructorInterface引入TypeScript并且每个函数文本都实现了它,那么就会出现向后不兼容。以下代码现在编译,但在引入ConstructorInterface时不编译

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.

当然,TS团队可以有一个平衡这两个选项的解决方案。但这不是一个高优先级。此外,如果salsa(TS驱动的JavaScript的代号)完全实现。这个问题自然会得到解决。

#2


4  

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.

它与扩展“EcmaScript 5”类相同。你声明了一个构造函数,根本没有原型。您可以毫无问题地扩展它。

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

但对于TypeScript,函数Fn(){}和类Fn {}之间存在很大差异。两者的类型不同。

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.

第一个是一个只返回任何函数的函数(TypeScript用()=> void显示它。第二个是构造函数。 TypeScript拒绝对非构造函数执行扩展。

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".

如果有一天javascript拒绝这样做,它将破坏许多javascript代码。因为目前,函数Fn(){}是在纯javascript中声明类的最常用方法。但从TypeScript的角度来看,这不是“类型安全”。

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

我认为TypeScript的唯一方法是使用类:

class Fn {} 

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

#3


1  

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:

我不确定我是否回答了你的问题,但我对如何通过TypeScript类扩展JS函数非常感兴趣,所以我尝试了以下内容:

fn.js (note the .js extension!)

fn.js(注意.js扩展名!)

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

c.ts

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.

请注意,这不是用于生产用途的代码,而是在没有时间将某些旧JS文件转换为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.

如果我处于OP状态,我会尝试将Fn转换为类,因为它使团队中的其他人更容易编写代码。

#4


1  

This is indirectly related to TypeScript interface to describe class

这与描述类的TypeScript接口间接相关

Although the typeof a class is function, there is no Class (nor Interface) in Typescript which is the super class of all classes.

虽然类的类型是函数,但是在Typescript中没有Class(也没有Interface),它是所有类的超类。

functions however are all interfaced by Function

但是函数都是由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...

我想与Javascript一致,函数应该是一个类,所有函数都应该是该类,所有类都应该扩展它...