web-dev-qa-db-ja.com

通常のES6クラスメゾッドから静的メソッドを呼び出す

静的メソッドを呼び出すための標準的な方法は何ですか?私はconstructorを使うこと、あるいはクラス自身の名前を使うことを考えることができます、後者はそれが必要であると感じないので好きではありません。前者はお勧めの方法ですか、それとも他に何かありますか?

これは(考案された)例です:

class SomeObject {
  constructor(n){
    this.n = n;
  }

  static print(n){
    console.log(n);
  }

  printN(){
    this.constructor.print(this.n);
  }
}
151
simonzack

どちらの方法も実行可能ですが、オーバーライドされた静的メソッドを使用した継承に関しては、動作が異なります。期待通りの動作を選択してください。

class Super {
  static whoami() {
    return "Super";
  }
  lognameA() {
    console.log(Super.whoami());
  }
  lognameB() {
    console.log(this.constructor.whoami());
  }
}
class Sub extends Super {
  static whoami() {
    return "Sub";
  }
}
new Sub().lognameA(); // Super
new Sub().lognameB(); // Sub

クラスを介して静的プロパティを参照することは実際には静的であり、常に同じ値を与えます。代わりにthis.constructorを使用すると、動的ディスパッチが使用され、現在のインスタンスのクラスが参照されます。静的プロパティmightは継承された値を持ちますが、オーバーライドすることもできます。

これは、クラス名またはインスタンスselfのいずれかを介して静的プロパティを参照するように選択できるPythonの動作と一致します。

静的プロパティが上書きされないように(そして常に現在のクラスの1つを参照するように)することを期待している場合、 Javaのように を明示的に参照してください。

185
Bergi

私はこのスレッドでつまずいて似たような場合に対する答えを探しました。基本的にすべての答えが見つかりましたが、それらから本質を引き出すのはまだ難しいです。

アクセスの種類

おそらく他のクラスから派生したクラスFooが、おそらく他のクラスから派生しているとします。

それからアクセス

  • staticから method/Fooのgetter
    • たぶん静的にオーバーライドされた method/getter:
      • this.method()
      • this.property
    • たぶん上書きされたインスタンス method/getter:
      • 設計上不可能
    • own 上書きされていない静的 method/getter:
      • Foo.method()
      • Foo.property
    • own 上書きされていないインスタンス method/getter:
      • 設計上不可能
  • インスタンスから method/get of Foo
    • たぶん静的にオーバーライドされた method/getter:
      • this.constructor.method()
      • this.constructor.property
    • たぶん上書きされたインスタンス method/getter:
      • this.method()
      • this.property
    • own 上書きされていない静的 method/getter:
      • Foo.method()
      • Foo.property
    • own 上書きされていないインスタンス method/getter:
      • 何らかの回避策を使用しない限り、意図的に不可能です
        • Foo.prototype.method.call( this )
        • Object.getOwnPropertyDescriptor( Foo.prototype,"property" ).get.call(this);

thisを使用しても、矢印関数を使用したり、カスタム値に明示的にバインドされたメソッド/ゲッターを呼び出したりする場合は、このようには機能しません。

バックグラウンド

  • インスタンスのメソッドまたはゲッターのコンテキスト内にある場合
    • thisは現在のインスタンスを参照しています。
    • superは基本的に同じインスタンスを参照していますが、現在のクラスのコンテキストで記述されたアドレス指定メソッドおよびゲッターは(Fooのプロトタイプのプロトタイプを使用して)拡張されています。
    • 作成時に使用されるインスタンスのクラスの定義はthis.constructorごとに利用可能です。
  • 静的メソッドまたはゲッターのコンテキスト内にあるときは、意図的に "現在のインスタンス"が存在しないため、
    • thisは、現在のクラスの定義を直接参照するために利用できます。
    • superはいくつかのインスタンスも参照していませんが、現在のクラスが拡張しているクラスのコンテキストで記述された静的メソッドおよびゲッターを参照しています。

結論

このコードを試してください:

class A {
  constructor( input ) {
    this.loose = this.constructor.getResult( input );
    this.tight = A.getResult( input );
    console.log( this.scaledProperty, Object.getOwnPropertyDescriptor( A.prototype, "scaledProperty" ).get.call( this ) );
  }

  get scaledProperty() {
    return parseInt( this.loose ) * 100;
  }
  
  static getResult( input ) {
    return input * this.scale;
  }
  
  static get scale() {
    return 2;
  }
}

class B extends A {
  constructor( input ) {
    super( input );
    this.tight = B.getResult( input ) + " (of B)";
  }
  
  get scaledProperty() {
    return parseInt( this.loose ) * 10000;
  }

  static get scale() {
    return 4;
  }
}

class C extends B {
  constructor( input ) {
    super( input );
  }
  
  static get scale() {
    return 5;
  }
}

class D extends C {
  constructor( input ) {
    super( input );
  }
  
  static getResult( input ) {
    return super.getResult( input ) + " (overridden)";
  }
  
  static get scale() {
    return 10;
  }
}


let instanceA = new A( 4 );
console.log( "A.loose", instanceA.loose );
console.log( "A.tight", instanceA.tight );

let instanceB = new B( 4 );
console.log( "B.loose", instanceB.loose );
console.log( "B.tight", instanceB.tight );

let instanceC = new C( 4 );
console.log( "C.loose", instanceC.loose );
console.log( "C.tight", instanceC.tight );

let instanceD = new D( 4 );
console.log( "D.loose", instanceD.loose );
console.log( "D.tight", instanceD.tight );
57
cepharum

あなたが何らかの継承をするつもりならば、私はthis.constructorをお勧めします。この簡単な例で、その理由を説明します。

class ConstructorSuper {
  constructor(n){
    this.n = n;
  }

  static print(n){
    console.log(this.name, n);
  }

  callPrint(){
    this.constructor.print(this.n);
  }
}

class ConstructorSub extends ConstructorSuper {
  constructor(n){
    this.n = n;
  }
}

let test1 = new ConstructorSuper("Hello ConstructorSuper!");
console.log(test1.callPrint());

let test2 = new ConstructorSub("Hello ConstructorSub!");
console.log(test2.callPrint());
  • test1.callPrint()ConstructorSuper Hello ConstructorSuper!をコンソールに記録します
  • test2.callPrint()ConstructorSub Hello ConstructorSub!をコンソールに記録します

名前付きクラスへの参照を作成するすべての関数を明示的に再定義しない限り、名前付きクラスは継承をうまく処理しません。これが一例です。

class NamedSuper {
  constructor(n){
    this.n = n;
  }

  static print(n){
    console.log(NamedSuper.name, n);
  }

  callPrint(){
    NamedSuper.print(this.n);
  }
}

class NamedSub extends NamedSuper {
  constructor(n){
    this.n = n;
  }
}

let test3 = new NamedSuper("Hello NamedSuper!");
console.log(test3.callPrint());

let test4 = new NamedSub("Hello NamedSub!");
console.log(test4.callPrint());
  • test3.callPrint()NamedSuper Hello NamedSuper!をコンソールに記録します
  • test4.callPrint()NamedSuper Hello NamedSub!をコンソールに記録します

Babel REPLで上記をすべて実行してください

test4はまだスーパークラスに属していると考えていることがわかります。この例では大したことには思えないかもしれませんが、オーバーライドされたメンバー関数または新しいメンバー変数を参照しようとしている場合は、困ったことに気付くでしょう。

17
Andrew Odri