In JavaScript, I can define a constructor function which can be called with or without new:
function MyCl
instanceof and extends workingThe problem with most of the solution I've seen to
use x = X() instead of x = new X()
are:
x instanceof X doesn't workclass Y extends X { } doesn't workconsole.log(x) prints some other type than Xx = X() works but x = new X() doesn'tUsing the code below (also on GitHub - see: ts-no-new) you can write:
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).
type cA = () => A;
function nonew(c: X): AI {
return (new Proxy(c, {
apply: (t, _, a) => new (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;
}
}
);
type NC = { new (): X };
type FC = { (): X };
type MC = NC & FC;
function nn(C: NC): MC {
return new Proxy(C, {
apply: (t, _, a) => new (t)(...a)
}) as MC;
}
class $A {
x: number;
constructor() {
this.x = 0;
}
a() {
return this.x += 1;
}
}
type A = $A;
const A: MC = nn($A);
Object.defineProperty(A, 'name', { value: 'A' });
class $B extends $A {
a() {
return this.x += 2;
}
}
type B = $B;
const B: MC = nn($B);
Object.defineProperty(B, 'name', { value: 'B' });
type NC = { new (): X };
type FC = { (): X };
type MC = NC & FC;
function nn(C: NC): MC {
return new Proxy(C, {
apply: (t, _, a) => new (t)(...a)
}) as MC;
}
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 = 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 = nn($B);
$B.$c = B;
Object.defineProperty(B, 'name', { value: 'B' });
type NC = { new (): X };
type FC = { (): X };
type MC = NC & FC;
function nn(C: NC): MC {
return new Proxy(C, {
apply: (t, _, a) => new (t)(...a)
}) as MC;
}
class $A {
x: number;
constructor() {
this.x = 0;
}
a() {
return this.x += 1;
}
}
type A = $A;
const A: MC = nn($A);
class $B extends $A {
a() {
return this.x += 2;
}
}
type B = $B;
const B: MC = nn($B);
type NC = { new (): X };
type FC = { (): X };
type MC = NC & FC;
function nn(C: NC): MC {
return new Proxy(C, {
apply: (t, _, a) => new (t)(...a)
}) as MC;
}
class $A {
x: number;
constructor() {
this.x = 10;
}
a() {
return this.x += 1;
}
}
type A = $A;
var A: MC = nn($A);
class $B extends $A {
a() {
return this.x += 2;
}
}
type B = $B;
var B: MC = nn($B);
In #1 and #2:
instanceof worksextends worksconsole.log prints correctlyconstructor property of instances point to the real constructorIn #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)The simplified versions don't provide all meta-data for introspection if you don't need it.