在没有 new 的情况下调用 TypeScript 类的构造函数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/32807163/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Call constructor on TypeScript class without new
提问by dan
In JavaScript, I can define a constructor function which can be called with or without new
:
在 JavaScript 中,我可以定义一个构造函数,可以使用或不使用它来调用new
:
function MyClass(val) {
if (!(this instanceof MyClass)) {
return new MyClass(val);
}
this.val = val;
}
I can then construct MyClass
objects using either of the following statements:
然后我可以MyClass
使用以下任一语句构造对象:
var a = new MyClass(5);
var b = MyClass(5);
I've tried to achieve a similar result using the TypeScript class below:
我尝试使用下面的 TypeScript 类获得类似的结果:
class MyClass {
val: number;
constructor(val: number) {
if (!(this instanceof MyClass)) {
return new MyClass(val);
}
this.val = val;
}
}
But calling MyClass(5)
gives me the error Value of type 'typeof MyClass' is not callable. Did you mean to include 'new'?
但是打电话MyClass(5)
给了我错误Value of type 'typeof MyClass' is not callable. Did you mean to include 'new'?
Is there any way I can make this pattern work in TypeScript?
有什么办法可以让这个模式在 TypeScript 中工作吗?
回答by jcalz
What about this? Describe the desired shape of MyClass
and its constructor:
那这个呢?描述所需的形状MyClass
及其构造函数:
interface MyClass {
val: number;
}
interface MyClassConstructor {
new(val: number): MyClass; // newable
(val: number): MyClass; // callable
}
Notice that MyClassConstructor
is defined as both callable as a function and newable as a constructor. Then implement it:
请注意,它MyClassConstructor
被定义为可调用的函数和可更新的构造函数。然后实现它:
const MyClass: MyClassConstructor = function(this: MyClass | void, val: number) {
if (!(this instanceof MyClass)) {
return new MyClass(val);
} else {
this!.val = val;
}
} as MyClassConstructor;
The above works, although there are a few small wrinkles. Wrinkle one: the implementation returns MyClass | undefined
, and the compiler doesn't realize that the MyClass
return value corresponds to the callable function and the undefined
value corresponds to the newable constructor... so it complains. Hence the as MyClassConstructor
at the end. Wrinkle two: the this
parameter does not currently narrow via control flow analysis, so we have to assert that this
is not void
when setting its val
property, even though at that point we know it can't be void
. So we have to use the non-null assertion operator !
.
以上工作,虽然有一些小皱纹。皱纹一:实现返回MyClass | undefined
,编译器没有意识到MyClass
返回值对应于可调用函数,undefined
值对应于newable构造函数......所以它会抱怨。因此as MyClassConstructor
在最后。问题二:this
参数当前没有通过控制流分析缩小范围,所以我们必须在设置它的属性时断言它this
不是,即使那时我们知道它不能是。所以我们必须使用非空断言运算符。void
val
void
!
Anyway, you can verify that these work:
无论如何,您可以验证这些工作:
var a = new MyClass(5); // MyClass
var b = MyClass(5); // also MyClass
Hope that helps; good luck!
希望有所帮助;祝你好运!
UPDATE
更新
Caveat: as mentioned in @Paleo's answer, if your target is ES2015 or later, using class
in your source will output class
in your compiled JavaScript, and those requirenew()
according to the spec. I've seen errors like TypeError: Class constructors cannot be invoked without 'new'
. It is quite possible that some JavaScript engines ignore the spec and will happily accept function-style calls also. If you don't care about these caveats (e.g., your target is explicitly ES5 or you know you're going to run in one of those non-spec-compliant environments), then you definitely can force TypeScript to go along with that:
警告:如@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
In this case you've renamed MyClass
out of the way to _MyClass
, and defined MyClass
to be both a type (the same as _MyClass
) and a value (the same as the _MyClass
constructor, but whose type is asserted to also be callable like a function.) This works at compile-time, as seen above. Whether your runtime is happy with it is subject to the caveats above. Personally I'd stick to the function style in my original answer since I know those are both callable and newable in es2015 and later.
在这种情况下,你已经改名MyClass
了的方式_MyClass
,并定义MyClass
为既是类型(同_MyClass
)和值(一样的_MyClass
构造,但其类型被肯定,也可调用的功能等)。这在编译时工作,如上所示。您的运行时是否满意取决于上述注意事项。就我个人而言,我会坚持原始答案中的函数风格,因为我知道这些在 es2015 及更高版本中都是可调用和可更新的。
Good luck again!
再次祝你好运!
UPDATE 2
更新 2
If you're just looking for a way of declaring the type of your bindNew()
function from this answer, which takes a spec-conforming class
and produces something which is both newable and callable like a function, you can do something like this:
如果您只是想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
}
This has the effect of correctly typing this:
这具有正确键入以下内容的效果:
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
But beware the the overloaded declarations for bindNew()
don't work for every possible case. Specifically it works for constructors which take up to three required parameters. Constructors with optional paramaters or multiple overload signatures will probably not be properly inferred. So you might have to tweak the typings depending on use case.
但要注意重载的声明bindNew()
不适用于所有可能的情况。具体来说,它适用于最多需要三个必需参数的构造函数。可能无法正确推断具有可选参数或多个重载签名的构造函数。因此,您可能需要根据用例调整类型。
Okay, hope thathelps. Good luck a third time.
好的,希望有帮助。祝你第三次好运。
UPDATE 3, AUG 2018
2018 年 8 月更新 3
TypeScript 3.0 introduced tuples in rest and spread positions, allowing us to easily deal with functions of an arbitrary number and type of arguments, without the above overloads and restrictions. Here's the new declaration of bindNew()
:
TypeScript 3.0在 rest 和 spread 位置引入了元组,使我们能够轻松处理任意数量和类型参数的函数,而没有上述重载和限制。这是 的新声明bindNew()
:
declare function bindNew<C extends { new(...args: A): T }, A extends any[], T>(
Class: C & { new(...args: A): T }
): C & ((...args: A) => T);
回答by Paleo
回答by rsp
Solution with instanceof
and extends
working
解决方案instanceof
和extends
工作
The problem with most of the solution I've seen to
use x = X()
instead of x = new X()
are:
与大多数我见过使用该解决方案的问题x = X()
,而不是x = new X()
有:
x instanceof X
doesn't workclass Y extends X { }
doesn't workconsole.log(x)
prints some other type thanX
- sometimes additionally
x = X()
works butx = new X()
doesn't - sometimes it doesn't work at all when targeting modern platforms (ES6)
x instanceof X
不起作用class Y extends X { }
不起作用console.log(x)
打印一些其他类型而不是X
- 有时也
x = X()
有效但x = new X()
无效 - 有时它在针对现代平台 (ES6) 时根本不起作用
My solutions
我的解决方案
TL;DR - Basic usage
TL;DR - 基本用法
Using the code below (also on GitHub - see: ts-no-new) you can write:
使用下面的代码(也在 GitHub 上 - 请参阅:ts-no-new)您可以编写:
interface A {
x: number;
a(): number;
}
const A = nn(
class A implements A {
x: number;
constructor() {
this.x = 0;
}
a() {
return this.x += 1;
}
}
);
or:
或者:
class $A {
x: number;
constructor() {
this.x = 10;
}
a() {
return this.x += 1;
}
}
type A = $A;
const A = nn($A);
instead of the usual:
而不是通常的:
class A {
x: number;
constructor() {
this.x = 0;
}
a() {
return this.x += 1;
}
}
to be able to use either a = new A()
or a = A()
with working instanceof
, extends
, proper inheritance and support for modern compilation targets (some solutions only work when transpiled to ES5 or older because they rely on class
translated to function
which have different calling semantics).
能够为使用a = new A()
或a = A()
与工作instanceof
,extends
,适当的继承和支持现代编译目标(一些解决方案,只有工作的时候transpiled到ES5以上的老年人,因为他们依靠class
翻译成function
具有不同的调用语义)。
Full examples
完整示例
#1
#1
type cA = () => A;
function nonew<X extends Function>(c: X): AI {
return (new Proxy(c, {
apply: (t, _, a) => new (<any>t)(...a)
}) as any as AI);
}
interface A {
x: number;
a(): number;
}
const A = nonew(
class A implements A {
x: number;
constructor() {
this.x = 0;
}
a() {
return this.x += 1;
}
}
);
interface AI {
new (): A;
(): A;
}
const B = nonew(
class B extends A {
a() {
return this.x += 2;
}
}
);
#2
#2
type NC<X> = { new (): X };
type FC<X> = { (): X };
type MC<X> = NC<X> & FC<X>;
function nn<X>(C: NC<X>): MC<X> {
return new Proxy(C, {
apply: (t, _, a) => new (<any>t)(...a)
}) as MC<X>;
}
class $A {
x: number;
constructor() {
this.x = 0;
}
a() {
return this.x += 1;
}
}
type A = $A;
const A: MC<A> = nn($A);
Object.defineProperty(A, 'name', { value: 'A' });
class $B extends $A {
a() {
return this.x += 2;
}
}
type B = $B;
const B: MC<B> = nn($B);
Object.defineProperty(B, 'name', { value: 'B' });
#3
#3
type NC<X> = { new (): X };
type FC<X> = { (): X };
type MC<X> = NC<X> & FC<X>;
function nn<X>(C: NC<X>): MC<X> {
return new Proxy(C, {
apply: (t, _, a) => new (<any>t)(...a)
}) as MC<X>;
}
type $c = { $c: Function };
class $A {
static $c = A;
x: number;
constructor() {
this.x = 10;
Object.defineProperty(this, 'constructor', { value: (this.constructor as any as $c).$c || this.constructor });
}
a() {
return this.x += 1;
}
}
type A = $A;
var A: MC<A> = nn($A);
$A.$c = A;
Object.defineProperty(A, 'name', { value: 'A' });
class $B extends $A {
static $c = B;
a() {
return this.x += 2;
}
}
type B = $B;
var B: MC<B> = nn($B);
$B.$c = B;
Object.defineProperty(B, 'name', { value: 'B' });
#2 simplified
#2 简化
type NC<X> = { new (): X };
type FC<X> = { (): X };
type MC<X> = NC<X> & FC<X>;
function nn<X>(C: NC<X>): MC<X> {
return new Proxy(C, {
apply: (t, _, a) => new (<any>t)(...a)
}) as MC<X>;
}
class $A {
x: number;
constructor() {
this.x = 0;
}
a() {
return this.x += 1;
}
}
type A = $A;
const A: MC<A> = nn($A);
class $B extends $A {
a() {
return this.x += 2;
}
}
type B = $B;
const B: MC<B> = nn($B);
#3 simplified
#3 简化
type NC<X> = { new (): X };
type FC<X> = { (): X };
type MC<X> = NC<X> & FC<X>;
function nn<X>(C: NC<X>): MC<X> {
return new Proxy(C, {
apply: (t, _, a) => new (<any>t)(...a)
}) as MC<X>;
}
class $A {
x: number;
constructor() {
this.x = 10;
}
a() {
return this.x += 1;
}
}
type A = $A;
var A: MC<A> = nn($A);
class $B extends $A {
a() {
return this.x += 2;
}
}
type B = $B;
var B: MC<B> = nn($B);
In #1and #2:
在#1和#2 中:
instanceof
worksextends
worksconsole.log
prints correctlyconstructor
property of instances point to the real constructor
instanceof
作品extends
作品console.log
打印正确constructor
实例的属性指向真正的构造函数
In #3:
在#3 中:
instanceof
worksextends
worksconsole.log
prints correctlyconstructor
property of instances point to the exposed wrapper (which may be an advantage or disadvantage depending on the circumstances)
instanceof
作品extends
作品console.log
打印正确constructor
实例的属性指向暴露的包装器(根据情况,这可能是优点或缺点)
The simplifiedversions don't provide all meta-data for introspection if you don't need it.
如果您不需要,简化版本不会提供所有用于内省的元数据。
See also
也可以看看
- My answer to: In TypeScript, can a class be used without the "new" keyword?
- My GitHub repo with more examples: https://github.com/rsp/ts-no-new
- 我的回答是:在 TypeScript 中,可以在没有“new”关键字的情况下使用类吗?
- 我的 GitHub 存储库和更多示例:https: //github.com/rsp/ts-no-new
回答by keos
My workaround with a type and a function:
我使用类型和函数的解决方法:
class _Point {
public readonly x: number;
public readonly y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
export type Point = _Point;
export function Point(x: number, y: number): Point {
return new _Point(x, y);
}
or with an interface:
或使用接口:
export interface Point {
readonly x: number;
readonly y: number;
}
class _PointImpl implements Point {
public readonly x: number;
public readonly y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
export function Point(x: number, y: number): Point {
return new _PointImpl(x, y);
}
回答by N. Kudryavtsev
TL;DR
TL; 博士
If you are targeting ES6 and you really want to use class
to store your data, not a function
:
如果你的目标是 ES6 并且你真的想用它class
来存储你的数据,而不是一个function
:
- Create a
function
that simply invokes your class constructor with its arguments; - Set that
function
'sprototype
to theprototype
of your class.
- 创建一个
function
简单地用它的参数调用你的类构造函数; - 设置
function
的prototype
给prototype
你的类。
From now you are able to call that function
either withor withoutnew
keyword to generate new class instances.
从现在开始,您function
可以使用或不使用new
关键字调用它来生成新的类实例。
Typescript provides an ability to create such a function
(let's call it a "callable constructor") in a strongly typed way. Well, any
type is necessary in intermediate type definitions (replacing it with unknown
causes errors), but this fact will notaffect your experience.
Typescript 提供了一种以function
强类型方式创建这样一个(我们称之为“可调用构造函数”)的能力。好吧,any
类型在中间类型定义中是必要的(用它代替会unknown
导致错误),但这个事实不会影响您的体验。
First of all we need to define basic types to describe entities we are working with:
首先,我们需要定义基本类型来描述我们正在使用的实体:
// Let's assume "class X {}". X itself (it has type "typeof X") can be called with "new" keyword,
// thus "typeof X" extends this type
type Constructor = new(...args: Array<any>) => any;
// Extracts argument types from class constructor
type ConstructorArgs<TConstructor extends Constructor> =
TConstructor extends new(...args: infer TArgs) => any ? TArgs : never;
// Extracts class instance type from class constructor
type ConstructorClass<TConstructor extends Constructor> =
TConstructor extends new(...args: Array<any>) => infer TClass ? TClass : never;
// This is what we want: to be able to create new class instances
// either with or without "new" keyword
type CallableConstructor<TConstructor extends Constructor> =
TConstructor & ((...args: ConstructorArgs<TConstructor>) => ConstructorClass<TConstructor>);
The next step is to write a function that accepts regular class constructors and creates corresponding "callable constructors".
下一步是编写一个函数,该函数接受常规类构造函数并创建相应的“可调用构造函数”。
function CreateCallableConstructor<TConstructor extends Constructor>(
type: TConstructor
): CallableConstructor<TConstructor> {
function createInstance(
...args: ConstructorArgs<TConstructor>
): ConstructorClass<TConstructor> {
return new type(...args);
}
createInstance.prototype = type.prototype;
return createInstance as CallableConstructor<TConstructor>;
}
Now all we have to do is to create our "callable constructor" and check it really works.
现在我们要做的就是创建我们的“可调用构造函数”并检查它是否真的有效。
class TestClass {
constructor(readonly property: number) { }
}
const CallableTestConstructor = CreateCallableConstructor(TestClass);
const viaCall = CallableTestConstructor(56) // inferred type is TestClass
console.log(viaCall instanceof TestClass) // true
console.log(viaCall.property) // 56
const viaNew = new CallableTestConstructor(123) // inferred type is TestClass
console.log(viaNew instanceof TestClass) // true
console.log(viaNew.property) // 123
CallableTestConstructor('wrong_arg'); // error
new CallableTestConstructor('wrong_arg'); // error