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


175

静的メソッドを呼び出す標準的な方法は何ですか?constructorクラス自体の名前を使用したり、使用したりすることは考えられますが、必要がないので後者は好きではありません。前者が推奨される方法ですか、それとも他に何かありますか?

これは(不自然な)例です:

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

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

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

8
SomeObject.print自然に感じます。しかしthis.n、静的メソッドの場合、インスタンスがないため、内部は意味がありません。
dfsq 2015

3
@dfsq printNは静的ではありません。
simonzack 2015

あなたは正しい、混乱した名前です。
dfsq 2015

1
この質問に賛成票があまりないのはなぜでしょうか。これはユーティリティ関数を作成するための一般的な方法ではありませんか?
Thoran

回答:


210

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

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代わりに、動的ディスパッチを使用して、静的プロパティは、現在のインスタンスのクラスに参照する可能性が継承された値を持っているだけでなく、上書きすることができます。

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

Javaのように、静的プロパティがオーバーライドされない(そして常に現在のクラスの1つを参照する)と予想される場合は、明示的な参照を使用します。


コンストラクタプロパティとクラスメソッド定義を説明できますか?
クリス

2
@Chris:すべてのクラスコンストラクター関数です(class構文なしのES5で知っているように)、メソッドの定義に違いはありません。それは、継承されたconstructorプロパティを介してまたは直接名前でどのように検索するかという問題です。
Bergi

別の例は、PHPの遅延静的バインディングです。this.constructorは継承を尊重するだけでなく、クラス名を変更した場合にコードを更新する必要がないようにするのにも役立ちます。
ricanontherun

@ricanontherun変数名を変更するときにコードを更新する必要があることは、名前の使用に対する反対ではありません。また、リファクタリングツールは、とにかくそれを自動的に行うことができます。
ベルギ

typescriptでこれを実装する方法は?エラーが発生しますProperty 'staticProperty' does not exist on type 'Function'
ayZagen

71

私はこのスレッドに出くわし、同様のケースに対する答えを探しました。基本的にすべての回答が見つかりますが、それらから本質的なものを抽出することはまだ困難です。

アクセスの種類

クラスFooがおそらく他のいくつかのクラスから派生し、さらに多くのクラスが派生していると仮定します。

次にアクセスする

  • Fooの静的メソッド/ゲッター から
    • おそらくオーバーライドされた静的メソッド/ゲッター:
      • this.method()
      • this.property
    • おそらくオーバーライドされたインスタンスメソッド/ゲッター:
      • 設計では不可能
    • オーバーライドされてない独自の静的メソッド/ゲッター:
      • Foo.method()
      • Foo.property
    • オーバーライドされてない独自のインスタンスメソッド/ゲッター:
      • 設計では不可能
  • Fooのインスタンスメソッド/ゲッター から
    • おそらくオーバーライドされた静的メソッド/ゲッター:
      • this.constructor.method()
      • this.constructor.property
    • おそらくオーバーライドされたインスタンスメソッド/ゲッター:
      • this.method()
      • this.property
    • オーバーライドされてない独自の静的メソッド/ゲッター:
      • Foo.method()
      • Foo.property
    • オーバーライドされてない独自のインスタンスメソッド/ゲッター:
      • いくつかの回避策を使用しない限り、意図的には不可能です:
        • 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 );


1
Own non-overridden instance method/getter / not possible by intention unless using some workaround---それは本当に残念です。私の意見では、これはES6 +の欠点です。たぶん、単にそれを参照できるように更新する必要がありますmethod-つまりmethod.call(this)。より良いFoo.prototype.method。バベルなど NFE(名前付き関数式)を使用して実装できます。
ロイティンカー2017年

method.call( this )isがmethod目的のベース「クラス」にバインドされていないことを除いて、おそらくソリューションであり、オーバーライドされていないインスタンスmethod / getterにはなりません。このようにして、クラスに依存しないメソッドを使用することは常に可能です。それにもかかわらず、現在のデザインはそれほど悪いとは思いません。基本クラスFooから派生したクラスのオブジェクトのコンテキストでは、インスタンスメソッドをオーバーライドする十分な理由がある場合があります。そのオーバーライドされたメソッドは、そのsuper実装を呼び出すかどうかにかかわらず、十分な理由がある場合があります。どちらのケースも適格であり、従う必要があります。そうでなければ、それは悪いOOP設計で終わります。
Thomas Urban、

OOPシュガーにもかかわらず、ESメソッドは依然として関数であり、人々はそれらをそのまま使用して参照したいと思うでしょう。ESクラス構文の私の問題は、それが現在実行中のメソッドへの直接参照を提供しないことです-以前は経由arguments.calleeまたはNFEで簡単でした。
ロイティンカー、

とにかく、悪い習慣や少なくとも悪いソフトウェア設計のように聞こえます。現在呼び出されているメソッドに参照でアクセスすることを含むOOPパラダイム(これは、を介して利用できるコンテキストだけではありません)のコンテキストに適格な理由がないので、私は両方の視点を互いに逆に考えますthis。ベアCのポインタ演算の利点と、より高レベルのC#を組み合わせようとするように思えます。好奇心から抜け出して:arguments.calleeきれいに設計されたOOPコードで何を使いますか?
トーマスアーバン

私はDojoのクラスシステムで構築された大規模なプロジェクトで作業しています。これにより、現在のメソッドのスーパークラスの実装を呼び出すことができます。this.inherited(currentFn, arguments);ここcurrentFnで、は現在実行中の関数への参照です。現在実行中の関数を直接参照できないことで、TypeScriptの機能が少し複雑になり、ES6からクラス構文が取得されます。
Roy Tinker

20

何らかの継承を計画している場合は、をお勧めし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これからも、スーパークラスにあると考えていることがわかります。この例では大したことのようには見えないかもしれませんが、オーバーライドされたメンバー関数または新しいメンバー変数を参照しようとすると、問題が発生します。


3
しかし、静的関数はオーバーライドされたメンバーメソッドではありませんか?通常あなたは試みていますではない静的に任意のオーバーライドされたものを参照すること。
Bergi、2015

1
@Bergi私はあなたが何を指摘しているのか私にはわかりませんが、私が遭遇した1つの特定のケースはMVCモデルのハイドレーションパターンです。モデルを拡張するサブクラスは、静的ハイドレート関数を実装する必要がある場合があります。ただし、これらがハードコードされている場合は、基本モデルのインスタンスのみが返されます。これはかなり具体的な例ですが、登録されたインスタンスの静的コレクションを持つことに依存する多くのパターンは、これによって影響を受けます。大きな免責事項の1つは、プロトタイプの継承ではなく、ここで古典的な継承をシミュレートしようとしていることです...そして、それは人気がありません:P
Andrew Odri

ええ、私が自分の答えで今結論付けたように、これは「クラシック」継承では一貫して解決されていません-オーバーライドが必要な場合もあれば、そうでない場合もあります。私のコメントの最初の部分は、「メンバー」であるとは考えていなかった静的クラス関数を指しています。無視してください:-)
Bergi
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.