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

时间:2016-08-03 22:46:28

标签: typescript

TypeScript是包含类型的ES6 Javascript的超集。可以使用class关键字声明一个类,并使用new关键字进行实例化,类似于它们在Java中的关键字。

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

我问的原因是因为我想知道是否假设我有一个名为Bob的类,我是否可以假设Bob的任何实例都使用new Bob()进行实例化。

2 个答案:

答案 0 :(得分:7)

默认情况下,

Typescript保护措施,所以如果你这样做:

class A {}
let a = A();

你会收到错误:

  

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

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

StringConstructor

interface StringConstructor {
    new (value?: any): String;
    (value?: any): string;
    ...
}

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[];
    ...
}

正如您所看到的那样,有new个关键字的情况总是相同的 如果你愿意,你当然可以模仿这种行为。

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

很容易检测到这是否在运行时发生,然后按照您认为合适的方式处理它(抛出错误,通过使用new返回实例来修复它并记录它。) 这是一篇关于它的帖子:Creating instances without 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 {}

code in playground

修改

据我所知,不可能让编译器理解A可以在没有new关键字的情况下调用any而不进行投射。
我们可以做得更好,而不是把它投到interface AConstructor { new(): A; (): A; } let a2 = (A as AConstructor)(); // A {}

Array

我们无法为lib.d.ts中的interface Array<T> { ... } interface ArrayConstructor { ... } declare const Array: ArrayConstructor; 执行诀窍的原因:

Array

在这里他们使用df = pd.DataFrame({'count': [False] * 12, 'index': [0, 1] * 6, 'cols': ['a', 'b', 'c'] * 4}) print(df) 一次作为一个类型而一次作为一个值,但是一个类既是一个类型又是一个值,所以试图做这个技巧将结束:

  

重复标识符'A'

答案 1 :(得分:0)

这是非常棘手的,但是可以通过多种方式完成,具体取决于您希望其与行为类似的程度。像这样的内置Array构造函数。

问题#1-没有'new'不能调用构造函数

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

在JavaScript中,如果没有class,则不能调用用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 {} 关键字创建的函数:

class

(有趣的是,如果将箭头功能和A()关键字构造函数都转换为ES6之前的JS,则上面的所有B()C()new都可以使用不论是否使用function,因为它们都将使用new关键字转换为旧样式函数,即使在当前引擎上也可以正常工作。)

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

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

在JavaScript(和TypeScript)中,this关键字创建一个新对象并将其绑定到构造函数中的function f() { console.log(this); } ,然后将其返回。因此,如果您有一个功能:

new f()

然后,如果您将其称为f(),它将打印并清空对象并将其返回。

如果您将其称为new而没有window,则它将打印全局对象(在浏览器中显示{global in Node orin 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自身any未定义`。 / p>

问题3-静态类型难以定义

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

问题4-制定出不一样的解决方案很容易

此问题再次不是特定于TypeScript而是针对JavaScript。 您希望一切都能按预期工作-使用旧函数和显式原型进行继承,并使用extendsclass关键字进行继承以及更多工作。

换句话说,该对象应与用new声明并用Array实例化且没有任何花哨内容的其他对象相同。

我的经验法则:如果您可以使用内置new之类的东西(无论是否使用A()都可以),那么您也应该使用我们的构造函数。

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

与JavaScript通用。 您想要的不仅是获得一个在没有new的情况下调用x instanceof A的对象,而且实际上您希望console.log()能够按预期工作,您还希望{{1} },以便在您要打印对象等时写下正确的名称。

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

问题6-使用老式的function

可以轻松解决问题

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

问题#7-一些解决方案仅在移植到ES5或更早版本时才有效

这与上面的问题#6 有关-如果转换目标是ES6之前的版本,则结果将使用旧式function构造函数,但不会产生错误:< / p>

TypeError: Class constructor A cannot be invoked without 'new'

(请参阅上面的问题1

这可能对您来说不是问题。如果您仍在为旧式引擎进行转译,则不会出现此问题,但是当您更改转译目标时(例如,避免async / await polyfills等的高运行时间成本),您会代码会中断。如果这是一个库代码,那么它将不适用于所有人。如果只供您自己使用,则至少要记住它。

解决方案

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

解决方案#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;
    }
  }
);

这样做的一个缺点是,虽然构造函数名称正确并且可以正常打印,但是constructor属性本身指向原始构造函数,该原始构造函数是nonew()函数的参数,而不是函数返回(取决于您如何掠夺它,这可能会或可能不会是一个问题)。

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

解决方案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' });

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

解决方案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' });

此解决方案的实例的constructor属性指向暴露的实例(不是$前缀的构造函数),但是使{{1}的constructor属性返回true }}-但是hasOwnProperty()false的地方,所以这应该不是问题。

更多解决方案

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

我对他们中的任何一个都不完全满意,但是他们都按照自己的方式工作。

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

的回答