在没有new的情况下调用TypeScript类上的构造函数

dan*_*dan 21 constructor typescript

在JavaScript中,我可以定义一个构造函数,可以使用或不使用调用new:

function MyClass(val) {
    if (!(this instanceof MyClass)) {
        return new MyClass(val);
    }

    this.val = val;
}
Run Code Online (Sandbox Code Playgroud)

然后我可以MyClass使用以下任一语句构造对象:

var a = new MyClass(5);
var b = MyClass(5);
Run Code Online (Sandbox Code Playgroud)

我尝试使用下面的TypeScript类获得类似的结果:

class MyClass {
    val: number;

    constructor(val: number) {
        if (!(this instanceof MyClass)) {
            return new MyClass(val);
        }

        this.val = val;
    }
}
Run Code Online (Sandbox Code Playgroud)

但是打电话MyClass(5)给了我错误Value of type 'typeof MyClass' is not callable. Did you mean to include 'new'?

有什么办法可以让这个模式在TypeScript中运行吗?

jca*_*alz 22

那这个呢?描述所需的形状MyClass及其构造函数:

interface MyClass {
  val: number;
}

interface MyClassConstructor {
  new(val: number): MyClass;  // newable
  (val: number): MyClass; // callable
}
Run Code Online (Sandbox Code Playgroud)

请注意,它MyClassConstructor被定义为可以作为函数调用并且可以作为构造函数使用.然后实现它:

const MyClass: MyClassConstructor = function(this: MyClass | void, val: number) {
  if (!(this instanceof MyClass)) {
    return new MyClass(val);
  } else {
    this!.val = val;
  }
} as MyClassConstructor;
Run Code Online (Sandbox Code Playgroud)

以上作品,虽然有一些小皱纹.皱一:实现返回MyClass | undefined,编译器不知道的是,MyClass返回值对应于可调用的函数和undefined值对应于newable构造...所以它抱怨.因此as MyClassConstructor最后.皱纹二:this参数目前没有通过控制流分析缩小,所以我们必须声明在设置其属性时this不是这样,即使在那时我们知道它不可能.所以我们必须使用非null断言运算符.voidvalvoid!

无论如何,你可以验证这些工作:

var a = new MyClass(5); // MyClass
var b = MyClass(5); // also MyClass
Run Code Online (Sandbox Code Playgroud)

希望有所帮助; 祝好运!


UPDATE

警告:正如在@ Paleo的回答中所提到的,如果您的目标是ES2015或更高版本,class在您的源代码中使用将class在您编译的JavaScript中输出,并且根据规范要求 new().我见过像这样的错误TypeError: Class constructors cannot be invoked without 'new'.一些JavaScript引擎很可能忽略了规范,并且也很乐意接受函数式调用.如果您不关心这些警告(例如,您的目标明确是ES5,或者您知道您将在其中一个非规范的环境中运行),那么您肯定可以强制使用TypeScript:

class _MyClass {
  val: number;

  constructor(val: number) {
    if (!(this instanceof MyClass)) {
      return new MyClass(val);
    }

    this.val = val;
  }
}
type MyClass = _MyClass;
const MyClass = _MyClass as typeof _MyClass & ((val: number) => MyClass)

var a = new MyClass(5); // MyClass
var b = MyClass(5); // also MyClass
Run Code Online (Sandbox Code Playgroud)

在这种情况下,你已经重命名MyClass_MyClass,并且定义MyClass为一个类型(相同_MyClass)和一个值(与_MyClass构造函数相同,但其类型被声明为也可以像函数一样调用.)如上所示,在编译时工作.您的运行时是否满意它受上述警告的影响.就个人而言,我会坚持原始答案中的函数风格,因为我知道在es2015及更高版本中它们都是可调用的和可更新的.

祝你好运!


更新2

如果你只是想bindNew()这个答案中寻找一种声明你的函数类型的方法,它需要一个符合规范class并产生像函数一样可以更新和可调用的东西,你可以这样做:

function bindNew<C extends { new(): T }, T>(Class: C & {new (): T}): C & (() => T);
function bindNew<C extends { new(a: A): T }, A, T>(Class: C & { new(a: A): T }): C & ((a: A) => T);
function bindNew<C extends { new(a: A, b: B): T }, A, B, T>(Class: C & { new(a: A, b: B): T }): C & ((a: A, b: B) => T);
function bindNew<C extends { new(a: A, b: B, d: D): T }, A, B, D, T>(Class: C & {new (a: A, b: B, d: D): T}): C & ((a: A, b: B, d: D) => T);
function bindNew(Class: any) {
  // your implementation goes here
}
Run Code Online (Sandbox Code Playgroud)

这有正确输入这个的效果:

class _MyClass {
  val: number;

  constructor(val: number) {    
    this.val = val;
  }
}
type MyClass = _MyClass;
const MyClass = bindNew(_MyClass); 
// MyClass's type is inferred as typeof _MyClass & ((a: number)=> _MyClass)

var a = new MyClass(5); // MyClass
var b = MyClass(5); // also MyClass
Run Code Online (Sandbox Code Playgroud)

但要注意重载的声明bindNew()不适用于所有可能的情况.具体来说,它适用于最多需要三个参数的构造函数.具有可选参数或多个过载签名的构造函数可能无法正确推断.因此,您可能需要根据用例调整输入.

好了,希望有助于.祝你好运第三次.


更新于2018年8月3日

TypeScript 3.0 在休息和传播位置引入了元组,允许我们轻松处理任意数量和类型的参数的函数,而不需要上述重载和限制.这是以下的新声明bindNew():

declare function bindNew<C extends { new(...args: A): T }, A extends any[], T>(
  Class: C & { new(...args: A): T }
): C & ((...args: A) => T);
Run Code Online (Sandbox Code Playgroud)


Pal*_*leo 5

newES6类需要该关键字:

但是,您只能通过new调用类,而不是通过函数调用(规范中的Sect.9.2.2)[source]