这个呢?描述MyClass的所需形状及其构造函数:
interface MyClass {
val: number;
}
interface MyClassConstructor {
new(val: number): MyClass; // newable
(val: number): MyClass; // callable
}
请注意,MyClassConstructor 被定义为既可作为函数调用,又可作为构造函数来新建。然后实现它:
const MyClass: MyClassConstructor = function(this: MyClass | void, val: number) {
if (!(this instanceof MyClass)) {
return new MyClass(val);
} else {
this!.val = val;
}
} as MyClassConstructor;
上述工作,虽然有一些小皱纹。皱纹一:实现返回MyClass | undefined,编译器没有意识到MyClass返回值对应于可调用函数,undefined值对应于newable构造函数......所以它抱怨。因此最后是as MyClassConstructor。皱纹二:this 参数does not currently narrow via control flow analysis,因此我们必须在设置其val 属性时断言this 不是void,即使此时我们知道它不可能是void。所以我们必须使用non-null assertion operator !。
无论如何,您可以验证这些工作:
var a = new MyClass(5); // MyClass
var b = MyClass(5); // also MyClass
希望有所帮助;祝你好运!
更新
警告:如@Paleo 的answer 中所述,如果您的目标是 ES2015 或更高版本,则在源代码中使用 class 将在您编译的 JavaScript 中输出 class,而那些 require @ 987654344@ 根据规范。我见过像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
在这种情况下,您已将MyClass 重命名为_MyClass,并将MyClass 定义为类型(与_MyClass 相同)和值(与@987654351 相同) @构造函数,但其类型被断言为也可以像函数一样调用。)这在编译时有效,如上所示。您的运行时是否对它满意取决于上述注意事项。就我个人而言,我会坚持原始答案中的函数样式,因为我知道它们在 es2015 及更高版本中都是可调用的和可更新的。
再次祝你好运!
更新 2
如果您只是在寻找一种从 this answer 声明 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
}
这具有正确输入以下内容的效果:
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
但要注意bindNew() 的重载声明并不适用于所有可能的情况。具体来说,它适用于最多需要三个必需参数的构造函数。可能无法正确推断具有可选参数或多个重载签名的构造函数。因此,您可能需要根据用例调整类型。
好的,希望 有所帮助。祝你第三次好运。
更新 3,2018 年 8 月
TypeScript 3.0 引入了tuples in rest and spread positions,允许我们轻松处理任意数量和类型的参数的函数,而没有上述重载和限制。这是bindNew()的新声明:
declare function bindNew<C extends { new(...args: A): T }, A extends any[], T>(
Class: C & { new(...args: A): T }
): C & ((...args: A) => T);