web-dev-qa-db-ja.com

ES6:新しいキーワードなしでクラスコンストラクターを呼び出す

単純なクラスを考える

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
66
user633183

クラスには「コンストラクター」という「クラス本体」があります。
内部constructor()関数を使用する場合、その関数も同じクラス本体であり、クラスが呼び出されたときに呼び出されるため、クラスは常にコンストラクターです。

コンストラクターは、new演算子を使用して新しいインスタンスを作成する必要があります。そのため、new演算子なしでクラスを呼び出すと、クラスコンストラクターの必須であるため、エラーが発生します新しいインスタンスを作成します。

エラーメッセージも非常に具体的で正しいです

TypeError: 'new'なしではクラスコンストラクターを呼び出すことはできません

あなたは出来る;

  • クラスの代わりに通常の関数を使用する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) };
37
adeneo

他の人が指摘したように、ES2015仕様では、このような呼び出しはTypeErrorをスローする必要があると厳密に述べていますが、同時に、目的の結果を正確に達成するために使用できる機能、つまり Proxies を提供しています。

プロキシを使用すると、オブジェクトの概念を仮想化できます。たとえば、特定のオブジェクトの一部の動作を、他に影響を与えることなく変更するために使用できます。

特定のユースケースでは、class FooFunction 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が公開したいクラスであることに注意してください。そのため、おそらく識別子は逆でなければなりません)

プロキシをサポートするブラウザーで実行する場合、_Foo(...)を呼び出すと、元のコンストラクターの代わりにapplyトラップ関数が実行されます。

同時に、この「新しい」_Fooクラスは、元のFooと区別できません(通常の関数として呼び出すことはできません)。同様に、Foo_Fooで作成されたオブジェクトを区別できる違いはありません。

この最大の欠点は、 トランスピールまたはポーリフィルできない ですが、将来的にScalaのようなクラスをJSに適用するための実行可能なソリューションです。

29
Adam Ples

ここに私が出会ったパターンがあり、それは本当に私を助けます。 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
21
user633183

いいえ、これは不可能です。 classキーワードを使用して作成されたコンストラクターは、 [[call]] ed である場合にのみ、newで構築できます。常になしでthrow a TypeError1 (そして、これを外部から検出する方法すらありません)。
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および静的メソッドは機能しませんが、Foo.prototype.constructor = Foo;およびObject.setPrototypeOf(Foo, _Foo) if必須。

_FooFooclass Bar extends Foo …ではなく)をサブクラス化するには、new _Foo呼び出しの代わりにreturn Reflect.construct(_Foo, args, new.target)を使用する必要があります。 ES5スタイルのサブクラス化(Foo.call(this, …)を使用)はできません。

12
Bergi
class MyClass {

  constructor(param) {
     // ...
  }

  static create(param) {
    return new MyClass(param);
  }

  doSomething() {
    // ...
  }

}

MyClass.create('Hello World').doSomething();

それはあなたが望むものですか?

MyClassの新しいインスタンスを作成するときに何らかのロジックが必要な場合、「CreationStrategy」を実装してロジックをアウトソーシングすると便利です。

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
Tim

私はあなたのためにこの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 
6

ドラフト でこれを掘り下げました

関数として呼び出されたときにクラス定義構文スローを使用して定義されたコンストラクター

だから、クラスでは不可能だと思います。

5
Joseph

ここで別の答えがありますが、これはかなり革新的だと思います。

基本的に、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をポリフィルする必要があります。また、Function.nameの目的との互換性のためにエクスポートに矢印関数を使用しないでください)

export function Right(...args){
  return prepareInstance(RightInstanceMethods,RightStaticMethods)(...args)
}

PS-prepareInstanceの新しい名前の提案を歓迎します。Gistを参照してください。

https://Gist.github.com/babakness/56da19ba85e0eaa43ae5577bc0064456

2
Babakness

他の回答に記載されている変換関数で変換されたクラスの拡張に問題がありました。問題は、ノード(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;
}());
2
kasbah

「スコープセーフコンストラクター」を使用できる場所は次のとおりです。このコードを確認してください。

function Student(name) {
  if(this instanceof Student) {
    this.name = name;
  } else {
    return new Student(name);
  }
}

次のように、newを使用せずにStudentオブジェクトを作成できるようになりました。

var stud1 = Student('Kia');
2
user2922935

クラスコンストラクターを手動で呼び出すと、コードをリファクタリングするときに役立ちます(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() {}
2
131

これを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()のようなことをします

0
Babakness

newキーワードなしでクラスコンストラクターを呼び出すことはできません。

エラーメッセージは非常に具体的です。

2ality および spec に関するブログ投稿を参照してください。

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