在TypeScript中,可以在没有"new"关键字的情况下使用类吗?

Dan*_*don 6 typescript

TypeScript是包含类型的ES6 Javascript的超集.可以使用class关键字声明一个类,并使用关键字实例化new它们,就像它们在Java中一样.

我想知道在TypeScript中是否有任何用例可以在不使用new关键字的情况下实例化类.

我问的原因是因为我想知道是否假设我有一个被调用的类Bob,我可以假设任何实例都Bob被实例化了new Bob().

Nit*_*mer 8

Typescript默认保护这一点,所以如果你这样做:

class A {}
let a = A();
Run Code Online (Sandbox Code Playgroud)

你会收到一个错误:

类型的typeof A值不可调用.你的意思是包括'新'吗?

但是,有些对象可以在不使用new关键字的情况下创建,基本上都是本机类型.
如果查看lib.d.ts,您可以看到不同构造函数的签名,例如:

StringConstructor:

interface StringConstructor {
    new (value?: any): String;
    (value?: any): string;
    ...
}
Run Code Online (Sandbox Code Playgroud)

ArrayConstructor:

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[];
    ...
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的那样,无论有没有new关键字,总会有相同的ctors .
如果你愿意,你当然可以模仿这种行为.

重要的是要理解的是,虽然打字稿检查以确保不会发生这种情况,但javascript不会检查,因此如果有人编写将使用您的代码的js代码,他可能会忘记使用new,所以这种情况仍有可能.

很容易检测到这是否在运行时发生,然后按照您认为合适的方式处理它(抛出错误,通过返回实例new并使用它来修复它).
这是一个讨论它的帖子:创建没有新(普通js)的实例,但是tl; dr是:

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 {}
Run Code Online (Sandbox Code Playgroud)

(游乐场代码)


编辑

据我所知,不可能让编译器理解A可以在没有new关键字的情况下调用它而不进行调用.
我们可以做得比把它投射到更好any:

interface AConstructor {
    new(): A;
    (): A;
}

let a2 = (A as AConstructor)(); // A {}
Run Code Online (Sandbox Code Playgroud)

我们无法做到(即)Arrayin中所做的诀窍的原因lib.d.ts:

interface Array<T> {
    ...
}

interface ArrayConstructor {
    ...
}

declare const Array: ArrayConstructor;
Run Code Online (Sandbox Code Playgroud)

在这里,他们使用Array一次作为一个类型,一次作为一个值,但一个类是一个类型和一个值,所以试图做这个技巧将结束:

重复的标识符'A'

  • 听起来你说的是简短的回答是"是". (3认同)

rsp*_*rsp 7

这是相当棘手的,但可以通过多种方式完成,具体取决于您希望它与像Array这样工作的内置构造函数的行为有多接近。

问题#1 - 没有“new”就无法调用构造函数

这不是 TypeScript 特有的,这是 JavaScript 的问题。

class在 JavaScript 中,如果没有关键字,则无法调用使用关键字创建的构造函数new

> class A {}
undefined
> new A()
A {}
> A()
TypeError: Class constructor A cannot be invoked without 'new'
Run Code Online (Sandbox Code Playgroud)

就像箭头函数不能用以下方式调用一样new

> B = () => {}
[Function: B]
> B()
undefined
> new B()
TypeError: B is not a constructor
Run Code Online (Sandbox Code Playgroud)

只有使用关键字创建的函数才function可以使用和不使用 来调用new

> function C() {}
undefined
> C()
undefined
> new C()
C {}
Run Code Online (Sandbox Code Playgroud)

(有趣的是,如果你将箭头函数和class关键字构造函数都转译为早于 ES6 的 JS A(),那么 allB()C()以上都将在有或没有的情况下工作new,因为它们都会被转译为带有function关键字的旧式函数,并且即使在当前的发动机。)

问题#2 - 构造函数在没有“new”的情况下无法获得正确的“this”

一旦克服了调用构造函数时出错的问题,您需要确保构造函数实际上获得了一个新对象。

在 JavaScript(和 TypeScript)中,new关键字创建一个新对象并将其绑定到this构造函数中,然后返回它。所以如果你有一个函数:

> class A {}
undefined
> new A()
A {}
> A()
TypeError: Class constructor A cannot be invoked without 'new'
Run Code Online (Sandbox Code Playgroud)

那么如果你调用它,new f()它将打印并清空对象并返回它。

如果您将其称为f()“没有” new,那么它将打印全局对象(window在浏览器中或global in Node or自我in web worker - see my module on npm [the-global-object](https://www.npmjs.com/package/the-global-object) for more info) and it will return未定义)。

问题 #3 - 静态类型很难定义

这个问题是 TypeScript 特有的。您需要确保所有类型都按预期工作并且以有用的方式工作。将所有内容声明为很容易any,但随后您将丢失编辑器中的所有提示,并且 TypeScript 编译器在编译期间不会检测到类型错误。

问题 #4 - 很容易制定出效果不同的解决方案

这个问题也不是 TypeScript 特有的,而是 JavaScript 普遍存在的。您希望一切都按预期工作 - 使用旧式函数和显式原型的继承以及使用classextends关键字进行继承以及更多。

换句话说,该对象应该与使用其他对象声明class和实例化的对象一样工作,new没有任何花哨的东西。

我的经验法则:如果您可以使用内置函数Array(例如,可以使用或不使用new)来执行某些操作,那么您也应该使用我们的构造函数来执行此操作。

问题 #5 - 使用不同的元数据很容易制定解决方案

再次普遍适用于 JavaScript。您想要的不仅是在调用时获得一个像您想要的那样工作的对象,A()而且new您实际上希望按x instanceof A预期工作,console.log()当您想要打印对象时,您想写出正确的名称等。

这可能不是每个人的问题,但需要考虑。

问题#6 - 使用老式方法很容易找到解决方案function

它应该支持class语法,而不是回到function构造函数和原型,否则你将失去许多有用的 TypeScript 功能。

问题 #7 - 某些解决方案仅在转换为 ES5 或更旧版本时才有效

这与上面的问题 #6有关- 如果转译目标是 ES6 之前的版本,那么结果将使用function不会给出错误的旧式构造函数:

TypeError: Class constructor A cannot be invoked without 'new'
Run Code Online (Sandbox Code Playgroud)

(参见上面的问题#1

这对您来说可能是问题,也可能不是问题。如果您无论如何都在为遗留引擎进行转译,那么您不会看到这个问题,但是当您更改转译目标时(例如,为了避免async/ awaitpolyfills 等的高运行时成本),那么您的代码将会崩溃。如果它是库代码,那么它并不适合所有人。如果它仅供您自己使用,那么至少请记住这一点。

解决方案

以下是我前段时间思考这个问题时想到的一些解决方案。我对它们并不是 100% 满意,我想避免使用代理,但这些是目前我发现的唯一可以解决上述所有问题的解决方案。

解决方案#1

我的第一次尝试之一(对于更一般的类型,请参阅后面的示例):

> B = () => {}
[Function: B]
> B()
undefined
> new B()
TypeError: B is not a constructor
Run Code Online (Sandbox Code Playgroud)

这样做的一个缺点是,虽然构造函数名称没问题并且打印得很好,但 constructor 属性本身指向作为nonew()函数参数的原始构造函数,而不是函数返回的内容(这可能是也可能不是问题,具体取决于关于你如何掠夺它)。

另一个缺点是需要声明接口来公开类型。

解决方案#2

另一个解决方案:

> function C() {}
undefined
> C()
undefined
> new C()
C {}
Run Code Online (Sandbox Code Playgroud)

在这里,您不需要在冗余接口中复制类型定义,而是获得带有$前缀的原始构造函数。在这里,您还可以获得继承和instanceof工作,构造函数名称和打印都可以,但 constructor 属性指向$- 前缀的构造函数。

解决方案#3

另一种方法:

function f() {
  console.log(this);
}
Run Code Online (Sandbox Code Playgroud)

该解决方案的 constructor 实例属性指向公开的(不是$- 前缀的构造函数),但使属性 constructor 返回- but for所以这应该不是问题。truehasOwnProperty()falsepropertyIsEnumerable()

更多解决方案

我把我所有的尝试和更多解释放在 GitHub 上:

我对他们中的任何一个都不是完全满意,但他们都在做自己的事情。

另请参阅我对Call constructor on TypeScript class without new 的回答