给出一个简单的类
class Foo {
constructor(x) {
if (!(this instanceof Foo)) return new Foo(x);
this.x = x;
}
hello() {
return `hello ${this.x}`;
}
}
是否可以在没有new
关键字的情况下调用类构造函数?
用法应该允许
(new Foo("world")).hello(); // "hello world"
或者
Foo("world").hello(); // "hello world"
但后者失败了
Cannot call a class as a function
答案 0 :(得分:37)
课程有一个"类主体" 是构造函数
如果使用内部constructor()
函数,那么该函数也将是同一个类体,并且将在调用类时调用,因此类始终是构造函数。
构造函数需要使用new
运算符来创建新实例,因此调用没有new
运算符的类会导致错误,因为它需要 < / em>为类构造函数创建一个新实例。
错误消息也非常具体,而且正确
TypeError:如果没有&#39; new&#39;
,则无法调用类构造函数
你可以;
new
。new
,这样你就可以获得类的好处,但是使用和不使用new
运算符仍然可以调用包装函数2 1)
function Foo(x) {
if (!(this instanceof Foo)) return new Foo(x);
this.x = x;
this.hello = function() {
return this.x;
}
}
2)
class Foo {
constructor(x) {
this.x = x;
}
hello() {
return `hello ${this.x}`;
}
}
var _old = Foo;
Foo = function(...args) { return new _old(...args) };
答案 1 :(得分:26)
正如其他人所指出的那样,ES2015规范严格规定此类调用应该抛出TypeError,但同时它提供了可用于实现所需结果的功能,即Proxies。
代理允许我们虚拟化对象的概念。例如,它们可用于更改特定对象的某些行为而不会影响其他任何内容。
在您的特定用例class Foo
中可以调用Function object
- 这通常意味着将执行此函数的主体。但是可以使用Proxy
更改此内容:
const _Foo = new Proxy(Foo, {
// target = Foo
apply (target, thisArg, argumentsList) {
return new target(...argumentsList);
}
});
_Foo("world").hello();
const f = _Foo("world");
f instanceof Foo; // true
f instanceof _Foo; // true
(请注意,_Foo
现在是您要公开的类,因此标识符应该是相反的方式)
如果由支持Proxies的浏览器运行,则调用_Foo(...)
现在将执行apply
陷阱功能而不是orignal构造函数。
同时,这个“新”_Foo
类与原始Foo
无法区分(除了能够将其称为普通函数)。类似地,您可以告诉用Foo
和_Foo
创建的对象没有区别。
这个问题的最大缺点是it cannot be transpilled or pollyfilled,但是将来可以在JS中使用类似Scala类的可行解决方案。
答案 2 :(得分:20)
这是我遇到的一种模式,真的对我很有帮助。它不使用class
,但也不需要使用new
。赢/赢。
const Foo = x => ({
x,
hello: () => `hello ${x}`,
increment: () => Foo(x + 1),
add: ({x: y}) => Foo(x + y)
})
console.log(Foo(1).x) // 1
console.log(Foo(1).hello()) // hello 1
console.log(Foo(1).increment().hello()) // hello 2
console.log(Foo(1).add(Foo(2)).hello()) // hello 3
答案 3 :(得分:12)
不,这是不可能的。使用class
关键字创建的构造函数只能使用new
构建,如果它们是[[call]]ed,则它们始终throw
TypeError
1 < / sup>(甚至没有办法从外面发现这种情况)
1:我不确定转发器是否正确
您可以使用普通功能作为解决方法:
class Foo {
constructor(x) {
this.x = x;
}
hello() {
return `hello ${this.x}`;
}
}
{
const _Foo = Foo;
Foo = function(...args) {
return new _Foo(...args);
};
Foo.prototype = _Foo.prototype;
}
免责声明:instanceof
并且Foo.prototype
正常工作,Foo.length
没有,.constructor
和静态方法没有,但可以通过添加{{}来修复1}}和Foo.prototype.constructor = Foo;
(如果需要)。
对于使用Object.setPrototypeOf(Foo, _Foo)
对Foo
(不是_Foo
)进行子类化,您应该使用class Bar extends Foo …
而不是return Reflect.construct(_Foo, args, new.target)
来电。无法以ES5样式(new _Foo
)进行子类化。
答案 4 :(得分:7)
我刚刚为你制作了这个npm模块;)
https://www.npmjs.com/package/classy-decorator
import classy from "classy-decorator";
@classy()
class IamClassy {
constructor() {
console.log("IamClassy Instance!");
}
}
console.log(new IamClassy() instanceof IamClassy); // true
console.log(IamClassy() instanceof IamClassy); // true
答案 5 :(得分:6)
class MyClass {
constructor(param) {
// ...
}
static create(param) {
return new MyClass(param);
}
doSomething() {
// ...
}
}
MyClass.create('Hello World').doSomething();
Is that what you want?
If you need some logic when creating a new instance of MyClass
, it could be helpful to implement a "CreationStrategy", to outsorce the logic:
class MyClassCreationStrategy {
static create(param) {
let instance = new MyClass();
if (!param) {
// eg. handle empty param
}
instance.setParam(param);
return instance;
}
}
class DefaultCreationStrategy {
static create(classConstruct) {
return new classConstruct();
}
}
MyClassCreationStrategy.create(param).doSomething();
DefaultCreationStrategy.create(MyClass).doSomething();
答案 6 :(得分:5)
答案 7 :(得分:2)
这是一个可以使用'范围安全构造函数'的地方 请注意以下代码:
function Student(name) {
if(this instanceof Student) {
this.name = name;
} else {
return new Student(name);
}
}
现在您可以创建一个Student对象而不使用new,如下所示:
var stud1 = Student('Kia');
答案 8 :(得分:2)
好吧我在这里有另一个答案,我觉得这个很有创意。
基本上,做与Naomik的答案类似的问题是,每次将方法链接在一起时,您都会创建函数。
编辑:这个解决方案有同样的问题,然而,这个答案是出于教育目的而留下的。
所以在这里,我提供了一种方法,只需将新值绑定到您的方法 - 这基本上只是独立的函数。这提供了额外的好处,能够将不同模块中的函数导入到新构造的对象中。
好的,就这样吧。
const assoc = (prop, value, obj) =>
Object.assign({},obj,{[prop]: value})
const reducer = ( $values, accumulate, [key,val] ) => assoc( key, val.bind( undefined,...$values ), accumulate )
const bindValuesToMethods = ( $methods, ...$values ) =>
Object.entries( $methods ).reduce( reducer.bind( undefined, ...$values), {} )
const prepareInstance = (instanceMethods, staticMethods = ({}) ) => Object.assign(
bindValuesToMethods.bind( undefined, instanceMethods ),
staticMethods
)
// Let's make our class-like function
const RightInstanceMethods = ({
chain: (x,f) => f(x),
map: (x,f) => Right(f(x)),
fold: (x,l,r) => r(x),
inspect: (x) => `Right(${x})`
})
const RightStaticMethods = ({
of: x => Right(x)
})
const Right = prepareInstance(RightInstanceMethods,RightStaticMethods)
现在你可以做到
Right(4)
.map(x=>x+1)
.map(x=>x*2)
.inspect()
您也可以
Right.of(4)
.map(x=>x+1)
.map(x=>x*2)
.inspect()
您还可以从模块中导出
export const Right = prepareInstance(RightInstanceMethods,RightStaticMethods)
虽然您没有获得ClassInstance.constructor
,但您确实拥有FunctorInstance.name
(请注意,您可能需要填充Function.name
和/或不使用箭头功能进行导出,以便与{的浏览器兼容{1}}目的)
Function.name
PS - 对readyInstance的新名称建议表示欢迎,请参阅Gist。
https://gist.github.com/babakness/56da19ba85e0eaa43ae5577bc0064456
答案 9 :(得分:2)
手动调用类构造函数在重构代码时很有用(在ES6中包含部分代码,其他部分包括函数和原型定义)
我最终得到了一个小而有用的样板,将构造函数切换到另一个函数中。周期。
class Foo {
constructor() {
//as i will not be able to call the constructor, just move everything to initialize
this.initialize.apply(this, arguments)
}
initialize() {
this.stuff = {};
//whatever you want
}
}
function Bar () {
Foo.prototype.initialize.call(this);
}
Bar.prototype.stuff = function() {}
答案 10 :(得分:1)
我在扩展使用其他答案中提到的转换函数转换的类时遇到了问题。问题似乎是节点(截至v9.4.0)不能正确支持参数传播运算符((...args) =>
)。
这个基于classy-decorator的转换输出的函数(在another answer中提到)对我有用,不需要支持装饰器或参数扩展运算符。
// function that calls `new` for you on class constructors, simply call
// YourClass = bindNew(YourClass)
function bindNew(Class) {
function _Class() {
for (
var len = arguments.length, rest = Array(len), key = 0;
key < len;
key++
) {
rest[key] = arguments[key];
}
return new (Function.prototype.bind.apply(Class, [null].concat(rest)))();
}
_Class.prototype = Class.prototype;
return _Class;
}
用法:
class X {}
X = bindNew(X);
// or
const Y = bindNew(class Y {});
const x = new X();
const x2 = X(); // woohoo
x instanceof X; // true
x2 instanceof X; // true
class Z extends X {} // works too
作为奖励,TypeScript(带有“es5”输出)似乎可以使用旧的instanceof
技巧(好吧,如果没有new
使用它就不会进行类型检查,但它无论如何都可以工作):
class X {
constructor() {
if (!(this instanceof X)) {
return new X();
}
}
}
因为它将其编译为:
var X = /** @class */ (function () {
function X() {
if (!(this instanceof X)) {
return new X();
}
}
return X;
}());
答案 11 :(得分:1)
您和其他人指出的
Foo("world").hello();
失败,因为它是一个错误, 根据ES6语法规则。
其他人指出
(new Foo("world")).hello();
可行,但由于
而笨拙我同意这很笨拙。所以我经常用 相反,此解决方案:
在您的类Foo中,创建一个静态方法 名为“新”:
static new (...args)
{ return new this (...args);
}
像这样使用它:
Foo.new("world").hello();
这样我就隐藏了“笨拙” 此静态方法“ new()”。
请注意,此方法new()是通用的, 它将照常工作 继承到子类时。如果你需要 要在子类中对其进行自定义,您可以先调用:
super.new(...args)
,然后在 子类中的方法,然后返回其结果。
答案 12 :(得分:0)
我将此添加为naomik评论的后续内容,并利用Tim和Bergi所说明的方法。我还建议使用of
函数作为一般情况。
要以功能方式执行此操作并利用原型的效率(每次创建新实例时不重新创建所有方法),可以使用此模式
const Foo = function(x){ this._value = x ... }
Foo.of = function(x){ return new Foo(x) }
Foo.prototype = {
increment(){ return Foo.of(this._value + 1) },
...
}
请注意,这与fantasy-land
JS规范
https://github.com/fantasyland/fantasy-land#of-method
我个人觉得使用ES6类语法更清晰
class Foo {
static of(x) { new Foo(x)}
constructor(x) { this._value = x }
increment() { Foo.of(this._value+1) }
}
现在可以将其包装在一个闭包中
class Foo {
static of(x) { new _Foo(x)}
constructor(x) { this._value = x }
increment() { Foo.of(this._value+1) }
}
function FooOf (x) {
return Foo.of(x)
}
或根据需要重命名FooOf
和Foo
,即班级可以是FooClass
,功能只是Foo
等。
这比将该类放在函数中更好,因为创建新实例也不会给我们带来创建新类的负担。
另一种方法是创建一个of
函数
const of = (classObj,...args) => (
classObj.of
? classObj.of(value)
: new classObj(args)
)
然后执行of(Foo,5).increment()
答案 13 :(得分:0)
无法使用new
关键字调用类构造函数。
错误消息非常具体。
However, you can only invoke a class via new, not via a function call (Sect. 9.2.2 in the spec):
> Point()
TypeError: Classes can’t be function-called
答案 14 :(得分:0)
答案 15 :(得分:0)
仍在寻找有趣的方法来使用instanceof
,而不必依赖new
或class
关键字。在此示例程序中,我们在不到一秒钟的时间内计算了第100,000个斐波纳契数。结果超过20,000位数字-
const fib = x =>
Loop // <- no `new`
( (n, a, b) =>
n <= 0n
? String(a) // <- no `new`
: Recur(n - 1n, b, a + b) // <- no `new`
, BigInt(x) // <- no `new`
, 0n
, 1n
)
function Loop (f, ...init)
{ let r = f(...init)
while (r instanceof Recur) // <- instanceof works
r = f(...r)
return r
}
function Recur (...v)
{ return Object.create // <- not a class, but works
( Recur.prototype // <- set prototype
, { constructor: { value: Recur } // <- set constructor
, [Symbol.iterator]: { value: _ => v.values() } // <- whatever you want
}
)
}
document.body.textContent = fib(100000)
body { overflow-wrap: anywhere; }
我不知道为什么我以前没想过-
function atom (T, v)
{ return Object.assign
( Object.create
( T.prototype
, { constructor: { value: T } }
)
, v
)
}
function pair (car, cdr)
{ return atom(pair, { car, cdr }) }
const p =
pair(1, 2)
console.log(p)
console.log(p instanceof pair)
输出-
{
"car": 1,
"cdr": 2
}
true
答案 16 :(得分:0)
我写了一个小辅助函数来解决这个问题。它有效地将 ES6 类转换为不受相同规则集约束的旧 ES5 构造函数。这样您就可以创建不需要 new
的构造函数。您还可以以类似于内置 Number
、String
等的方式重载构造函数。
function callableConstructor(c, f) {
function ret(...args) {
if(new.target) {
return new c(...args)
}
return f(...args)
}
ret.prototype = c.prototype
ret.prototype.constructor = ret
return ret
}
在下面测试:
function callableConstructor(c, f) {
function ret(...args) {
if(new.target) {
return new c(...args)
}
return f(...args)
}
ret.prototype = c.prototype
ret.prototype.constructor = ret
return ret
}
// Usage
class Foo {
constructor(a, b) {
this.a = a
this.b = 2 * b
}
f() {
return this.a + this.b
}
}
Foo = callableConstructor(Foo, (...args) => new Foo(...args))
let foo = new Foo(2, 3)
console.log(foo) // Foo { a: 2, b: 6 }
console.log(foo.f()) // 8
console.log(foo instanceof Foo) // true
foo = Foo(2, 3)
console.log(foo) // Foo { a: 2, b: 6 }
console.log(foo.f()) // 8
console.log(foo instanceof Foo) // true
答案 17 :(得分:-1)
这可能有点人为,但它有效
function Foo(x){
"use strict"
class Bar {
constructor(x) {
if (!(this instanceof Bar)) return new Bar(x);
this.x = x;
}
hello() {
return `hello ${this.x}`;
}
}
return new Bar(x)
}
Foo("world").hello()