ES6:没有new关键字的调用类构造函数

时间:2015-06-07 03:41:57

标签: javascript constructor ecmascript-6 instance

给出一个简单的类

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

18 个答案:

答案 0 :(得分:37)

课程有一个"类主体" 是构造函数 如果使用内部constructor()函数,那么该函数也将是同一个类体,并且将在调用类时调用,因此类始终是构造函数。

构造函数需要使用new运算符来创建新实例,因此调用没有new运算符的类会导致错误,因为它需要 < / em>为类构造函数创建一个新实例。

错误消息也非常具体,而且正确

  

TypeError:如果没有&#39; new&#39;

,则无法调用类构造函数

你可以;

  • 使用常规函数而不是类 1
  • 始终使用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)

the draft

中挖出这个
  

使用类定义语法定义的构造函数在作为函数调用时抛出

所以我猜这对于课程是不可能的。

答案 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();

可行,但由于

而笨拙
  • 它需要“新” AND
  • 需要额外的括号。

我同意这很笨拙。所以我经常用 相反,此解决方案:

  1. 在您的类Foo中,创建一个静态方法 名为“新”:

     static new (...args)
     { return new this (...args);
     } 
    
  2. 像这样使用它:

      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)

}

或根据需要重命名FooOfFoo,即班级可以是FooClass,功能只是Foo等。

这比将该类放在函数中更好,因为创建新实例也不会给我们带来创建新类的负担。

另一种方法是创建一个of函数

const of = (classObj,...args) => (
  classObj.of 
    ? classObj.of(value) 
    : new classObj(args)
)

然后执行of(Foo,5).increment()

之类的操作

答案 13 :(得分:0)

无法使用new关键字调用类构造函数。

错误消息非常具体。

查看2alityspec上的博文:

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)

没有新的构造函数就不能使用类,就我而言,在我想使用我的类的任何时候,我都不想使用new构造函数,因此您可以做的是包装您的类如下所示的类(在我的情况下,这是一个Dates utils库):

enter image description here

答案 15 :(得分:0)

仍在寻找有趣的方法来使用instanceof,而不必依赖newclass关键字。在此示例程序中,我们在不到一秒钟的时间内计算了第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 的构造函数。您还可以以类似于内置 NumberString 等的方式重载构造函数。

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()