从非类继承ES6/TS类

Inherit ES6/TS class from non-class

本文关键字:TS ES6 继承      更新时间:2023-09-26

如果类是从非类(包括但不限于函数)扩展而来的,

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

后果是什么?规格说明是什么?

看起来目前Babel、Google V8和Mozilla Spidermonkey的实现都可以,TypeScript抛出

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

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

TypeScript部分

到目前为止,规范规定extends子句后面必须跟一个TypeReference。TypeReference的形式必须是A.B.C<TypeArgument>,就像MyModule.MyContainer<MyItem>一样。因此,从语法上讲,您的代码是正确的。但这不是打字的情况。

规范规定BaseClass必须是一个有效的typescript类。然而,正如这里所说,规范已经过时了。现在TypeScript允许在extends子句中使用表达式,只要表达式是根据构造函数计算的。这个定义是基于实现的。你可以在这里看到。简单地说,如果一个表达式实现了new() {}接口,那么它就可以算作构造函数。

ES2015部分

因此,您的问题是普通函数在TypeScript中未被识别为构造函数,这是有争议的,因为ES2015规范只要求对象具有[[construct]]内部方法。而用户定义的函数对象确实有它。

ES2015要求BaseClass运行时的构造函数。一个对象isConstructor,如果它有[[construct]]内部方法。规范中说[[construct]]是函数对象的内部方法。用户函数是Function Objects的实例,所以它们自然是构造函数。但是内置函数和箭头函数不能有[[construct]]

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

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

和来自ECMAScript

箭头函数和内置函数一样,都缺少.protype和任何[[Construct]]内部方法。因此new(()=>{})抛出一个TypeError,但除此之外,箭头类似于函数:

根据经验,任何没有prototype的函数都不能使用new

解决方案

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

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

interface FnType {}
var Fn: {new(): FnType} = (function() {}) as any
class B extends Fn {}

推理不相容

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

TypeScript是一个起源于2012年的项目,当时ES2015还在昏暗的黑暗中若隐若现。TypeScript对类语义没有很好的引用。

当时,TypeScript的主要目标是与ES3/5保持兼容。因此,new调用函数在TypeScript中是合法的,因为它在ES3/5中也是合法的。同时,TypeScript还旨在捕获编程错误。extends函数可能是一个错误,因为该函数可能不是一个合理的构造函数(例如,一个仅用于副作用的函数)。extends甚至不存在于ES3/5中!因此TypeScript可以自由定义自己对extends的使用,使得extends必须与class变量配对。这使得TypeScript在与JavaScript兼容的同时具有更多的TypeSafe

现在,ES2015规范已经定稿。JavaScript还有一个extends关键字!然后不兼容就来了。正在努力解决不兼容问题。然而,问题仍然存在。如上所述,由于内置函数,() => voidFunction类型不应为扩展功能。以下代码将破坏

var a: (x: string) => void = eval
new a('booom')

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

var a = function (s) {}
a = parseInt // compile error because parseInt is not assignable to constructor

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

后果是什么?规格说明是什么?

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

但对于TypeScript,function Fn() {}class Fn {}之间有很大的区别。两者不属于同一类型。

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

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

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

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

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

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

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

然后我跑了:

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

输出为:

1
2
3
Hello!

请注意,这不是用于生产的代码,而是当您没有时间将一些旧的JS文件转换为TypeScript时的一种变通方法。

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

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

虽然typeofa类是function,但Typescript中没有class(也没有Interface),它是所有类中的超类。

然而,所有功能都由Function 接口

我想为了与Javascript一致,Function可能应该是一个类,所有函数都应该是该类的,所有类都应该扩展它…