TypeScript是ES6 Javascript的一个超集,它包含类型。使用 class
关键字声明类,并使用 new
关键字实例化类,就像在Java中一样。
我想知道在TypeScript中是否有任何情况下可以不使用 new
关键字实例化类。
我之所以问这个问题,是因为我想知道,假设我有一个名为 Bob
的类,我能否假定任何 Bob
的实例都是用 new Bob()
实例化的。
TypeScript是ES6 Javascript的一个超集,它包含类型。使用 class
关键字声明类,并使用 new
关键字实例化类,就像在Java中一样。
我想知道在TypeScript中是否有任何情况下可以不使用 new
关键字实例化类。
我之所以问这个问题,是因为我想知道,假设我有一个名为 Bob
的类,我能否假定任何 Bob
的实例都是用 new Bob()
实例化的。
class A {}
let a = A();
您会收到一个错误:
类型
typeof A
的值不可调用。您是否意味着包括 'new'?
但是,有一些对象可以在不使用 new
关键字的情况下创建,基本上所有的原生类型都可以。
如果您查看 lib.d.ts,可以看到不同构造函数的签名,例如:
interface StringConstructor {
new (value?: any): String;
(value?: any): string;
...
}
interface ArrayConstructor {
new (arrayLength?: number): any[];
new <T>(arrayLength: number): T[];
new <T>(...items: T[]): T[];
(arrayLength?: number): any[];
<T>(arrayLength: number): T[];
<T>(...items: T[]): T[];
...
}
您可以看到,始终存在带有和不带有new
关键字的相同构造函数。
如果您愿意,当然可以模仿这种行为。
重要的是要理解,尽管 TypeScript 会检查以确保不会发生这种情况,但 JavaScript 不会检查,因此如果有人编写将使用您的代码的 js 代码,他可能会忘记使用new
,因此仍然存在这种情况。
在运行时很容易检测是否发生了这种情况,然后根据需要处理它(抛出错误、使用new
返回实例并记录)。
这里有一篇讨论它的文章:创建实例而不使用 new(纯 js),但简而言之:
class A {
constructor() {
if (!(this instanceof A)) {
// throw new Error("A was instantiated without using the 'new' keyword");
// console.log("A was instantiated without using the 'new' keyword");
return new A();
}
}
}
let a1 = new A(); // A {}
let a2 = (A as any)(); // A {}
据我所知,编译器无法理解可以在不使用new
关键字的情况下调用A
,除非将其转换为any
。
我们可以做得比将其强制转换为any
更好:
interface AConstructor {
new(): A;
(): A;
}
let a2 = (A as AConstructor)(); // A {}
我们无法像在 lib.d.ts
中的 Array
那样进行技巧操作的原因是:
interface Array<T> {
...
}
interface ArrayConstructor {
...
}
declare const Array: ArrayConstructor;
这里他们一次将 Array
用作类型,一次用作值,但是类既是类型又是值,因此尝试使用此技巧将以以下方式结束:
重复标识符“A”
这是一个相当棘手的问题,但可以通过多种方式解决,具体取决于您希望它与内置Array
构造函数的行为有多接近。
这不仅适用于TypeScript,也适用于JavaScript。
使用class
关键字创建的构造函数在JavaScript中无法在没有new
的情况下调用:
> class A {}
undefined
> new A()
A {}
> A()
TypeError: Class constructor A cannot be invoked without 'new'
就像箭头函数不能使用 new
关键字调用一样:
> B = () => {}
[Function: B]
> B()
undefined
> new B()
TypeError: B is not a constructor
只有使用function
关键字创建的函数才可以通过new
关键字调用:
> function C() {}
undefined
> C()
undefined
> new C()
C {}
function f() {
console.log(this);
}
如果你以new f()
的形式调用它,它将打印一个空对象并返回它。
如果你不使用new
以f()
的形式调用它,则会打印全局对象(在浏览器中为window
,在Node中为global
或在Web Worker中为self
- 有关更多信息,请查看我在npm上的模块[the-global-object](https://www.npmjs.com/package/the-global-object)),并且它将返回undefined
。
这个问题是特定于TypeScript的。您需要确保所有类型按预期工作,并且它们以有用的方式工作。将所有内容声明为any
很容易,但是这样您将失去编辑器中的所有提示,并且TypeScript编译器将无法在编译期间检测到类型错误。
这个问题再次不特定于TypeScript而是一般适用于JavaScript。
您希望所有内容都按预期工作-使用旧式函数和显式原型进行继承以及使用class
和extends
关键字进行继承等。
换句话说,该对象应与使用class
声明并使用new
实例化的其他对象一样工作,没有花哨的东西。
我的经验法则:如果您可以使用内置函数(例如Array
)执行某些操作(既可以带有new
也可以不带),那么您也应该使用我们的构造函数来执行此操作。
再次适用于JavaScript。
您想要的不仅是在调用A()
而不使用new
时按预期工作的对象,而且您实际上希望x instanceof A
按预期工作,您希望console.log()
在打印对象时写入正确的名称等。
这可能对每个人都不是一个问题,但需要考虑。
function
的解决方案它应支持class
语法,而不是回到function
构造函数和原型,否则您将失去许多有用的TypeScript功能。
这与上面的问题#6有关-如果转换目标是ES6之前的版本,则结果将使用旧式function
构造函数,这不会产生错误:
TypeError: Class constructor A cannot be invoked without 'new'
async
/await
填充等高运行时成本)时,你的代码将会出现问题。如果它是一个库代码,则不会适用于所有人。如果仅供您自己使用,则至少要记住这一点。
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;
}
}
);
其中一个缺点是,虽然构造函数名称正确并且可以正常打印,但是 constructor
属性本身指向作为参数传递给 nonew()
函数的原始构造函数,而不是指向函数返回的内容(这可能是个问题,具体取决于您如何看待它)。
另一个缺点是需要声明接口以公开类型。
另一种解决方案:
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' });
这里不需要在冗余的接口中重复类型定义,而是可以使用带有$
前缀的原始构造函数。
这里还可以使用继承和instanceof
,构造函数名称和打印都可以正常工作,但constructor
属性指向带有$
前缀的构造函数。
另一种方法:
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' });
这种解决方案使实例的constructor
属性指向公开的构造函数(而不是以$
为前缀的构造函数),但使constructor
属性返回true
,从而可以使用hasOwnProperty()
- 但对于propertyIsEnumerable()
返回false
,因此这应该不是问题。
我将所有尝试以及一些更多的解释都放在了 GitHub 上:
我并不完全满意其中任何一种,但它们都能够完成任务。
另请参阅我的回答:在没有new的情况下调用TypeScript类的构造函数
AContrcutor
类型,然后执行 const a = (A asAContrcutor)()
即可。你们所有的解决方案都试图避免这种方式,但最终变得过于冗长,不仅增加了太多的 ts 代码,而且还会导致更多冗余的运行时(js)代码。 - Nitzan Tomernew
关键字,那么其他关键字也应该不引入。否则我们可以更容易地为每个类添加一个静态工厂函数,称为例如create
并使用a = A.create()
而不是a = new A()
,但我认为a = new A()
没有new
应该实际上是a = A()
,仅此而已。也许这不是OP所想的,但到目前为止还没有接受的答案,所以我们还不知道。我的自己的需求在github上描述(在答案中)如果你有一个想法来完成它没有代理让我知道。 - rspa = A()
必须等同于a = new A()
,instanceof
也必须正常工作。a = A()
等同于a = new A()
是一个硬性要求。如果您有简化它的想法,那么我非常乐意听取您的意见,就像我在https://github.com/rsp/ts-no-new上所写的一样,但是告诉我“拥有一个可替换的Array并不是很重要”令人失望。我已经解决了这个问题,如果您有更好的解决方案,请让我知道。 - rspconst nonew = c => new Proxy(X, { apply(t, i, a) { return new t(...a); } });
其他的都是打字和确保返回的元数据正确,但如果您不关心元数据或者您不介意使用 any
类型,那么它就非常简单。至于增加的运行时成本,我不会在没有进行基准测试的情况下抱怨。 - rsp
new
的情况下调用构造函数(但这种情况下你将没有实例)。 - Nitzan TomerA()
一起工作,而不是(A as any)()
? - rsp