Rufen Sie statische Methoden aus regulären ES6-Klassenmethoden auf

175

Wie werden statische Methoden standardmäßig aufgerufen? Ich kann mir vorstellen constructor, den Namen der Klasse selbst zu verwenden oder zu verwenden. Letzteres gefällt mir nicht, da es sich nicht notwendig anfühlt. Ist der erstere der empfohlene Weg oder gibt es noch etwas anderes?

Hier ist ein (erfundenes) Beispiel:

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

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

  printN(){
    this.constructor.print(this.n);
  }
}
Simonzack
quelle
8
SomeObject.printfühlt sich natürlich an. Aber this.ninnen macht es keinen Sinn, da es keine Instanz gibt, wenn es um statische Methoden geht.
dfsq
3
@dfsq printNist jedoch nicht statisch.
Simonzack
Sie haben Recht, verwirrte Namen.
dfsq
1
Ich bin gespannt, warum diese Frage nicht so viele positive Stimmen hat! Ist dies nicht eine gängige Praxis zum Erstellen von Dienstprogrammfunktionen?
Thoran

Antworten:

210

Beide Methoden sind realisierbar, aber sie bewirken unterschiedliche Dinge, wenn es um die Vererbung mit einer überschriebenen statischen Methode geht. Wählen Sie denjenigen aus, dessen Verhalten Sie erwarten:

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

Das Verweisen auf die statische Eigenschaft über die Klasse ist tatsächlich statisch und gibt ständig den gleichen Wert an. Wenn Sie this.constructorstattdessen verwenden, wird der dynamische Versand verwendet und auf die Klasse der aktuellen Instanz verwiesen, in der die statische Eigenschaft möglicherweise den geerbten Wert hat, aber auch überschrieben werden kann.

Dies entspricht dem Verhalten von Python, bei dem Sie entweder über den Klassennamen oder die Instanz auf statische Eigenschaften verweisen können self.

Wenn Sie erwarten, dass statische Eigenschaften nicht überschrieben werden (und sich immer auf die der aktuellen Klasse beziehen), wie in Java , verwenden Sie die explizite Referenz.

Bergi
quelle
Können Sie die Definition der Konstruktoreigenschaft gegenüber der Klassenmethode erklären?
Chris
2
@Chris: Jede Klasse ist eine Konstruktorfunktion (genau wie Sie sie von ES5 ohne classSyntax kennen), es gibt keinen Unterschied in der Definition der Methode. Es geht nur darum, wie Sie es nachschlagen, über die geerbte constructorEigenschaft oder direkt nach ihrem Namen.
Bergi
Ein weiteres Beispiel sind die späten statischen Bindungen von PHP . This.constructor berücksichtigt nicht nur die Vererbung, sondern verhindert auch, dass Sie den Code aktualisieren müssen, wenn Sie den Klassennamen ändern.
Ricanontherun
@ricanontherun Das Aktualisieren des Codes beim Ändern von Variablennamen ist kein Argument gegen die Verwendung von Namen. Auch Refactoring-Tools können dies ohnehin automatisch tun.
Bergi
Wie implementiere ich dies in Typoskript? Es gibt den FehlerProperty 'staticProperty' does not exist on type 'Function'
ayZagen
71

Ich stolperte über diesen Thread und suchte nach einer Antwort auf einen ähnlichen Fall. Grundsätzlich sind alle Antworten gefunden, aber es ist immer noch schwierig, das Wesentliche daraus zu extrahieren.

Arten des Zugangs

Angenommen, eine Klasse Foo stammt wahrscheinlich von einer anderen Klasse (n), von der wahrscheinlich mehr Klassen abgeleitet sind.

Dann zugreifen

  • von der statischen Methode / Getter von Foo
    • einige wahrscheinlich überschriebene statische Methode / Getter:
      • this.method()
      • this.property
    • einige wahrscheinlich überschriebene Instanzmethode / Getter:
      • unmöglich durch Design
    • eigene nicht überschriebene statische Methode / Getter:
      • Foo.method()
      • Foo.property
    • eigene nicht überschriebene Instanzmethode / Getter:
      • unmöglich durch Design
  • von Instanzmethode / Getter von Foo
    • einige wahrscheinlich überschriebene statische Methode / Getter:
      • this.constructor.method()
      • this.constructor.property
    • einige wahrscheinlich überschriebene Instanzmethode / Getter:
      • this.method()
      • this.property
    • eigene nicht überschriebene statische Methode / Getter:
      • Foo.method()
      • Foo.property
    • eigene nicht überschriebene Instanzmethode / Getter:
      • absichtlich nicht möglich, es sei denn, Sie verwenden eine Problemumgehung :
        • Foo.prototype.method.call( this )
        • Object.getOwnPropertyDescriptor( Foo.prototype,"property" ).get.call(this);

Beachten Sie, dass die Verwendung thisnicht auf diese Weise funktioniert, wenn Sie Pfeilfunktionen verwenden oder Methoden / Getter aufrufen, die explizit an benutzerdefinierte Werte gebunden sind.

Hintergrund

  • Im Kontext der Methode oder des Getters einer Instanz
    • this bezieht sich auf die aktuelle Instanz.
    • super bezieht sich im Grunde genommen auf dieselbe Instanz, aber etwas Adressierungsmethoden und Getter, die im Kontext einer aktuellen Klasse geschrieben wurden, werden erweitert (unter Verwendung des Prototyps von Foos Prototyp).
    • Die Definition der Instanzklasse, die beim Erstellen verwendet wird, ist per verfügbar this.constructor.
  • Wenn es im Kontext einer statischen Methode oder eines Getters keine "aktuelle Instanz" gibt, ist dies beabsichtigt
    • this steht zur Verfügung, um direkt auf die Definition der aktuellen Klasse zu verweisen.
    • super bezieht sich auch nicht auf eine Instanz, sondern auf statische Methoden und Getter, die im Kontext einer Klasse geschrieben wurden, die derzeit erweitert wird.

Fazit

Versuchen Sie diesen Code:

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

Thomas Urban
quelle
1
Own non-overridden instance method/getter / not possible by intention unless using some workaround--- Das ist wirklich schade. Meiner Meinung nach ist dies ein Mangel von ES6 +. Vielleicht sollte es aktualisiert werden, um einfach darauf zu verweisen method- dh method.call(this). Besser als Foo.prototype.method. Babel / etc. könnte mit einem NFE (benannter Funktionsausdruck) implementiert werden.
Roy Tinker
method.call( this )ist eine wahrscheinliche Lösung, außer dass die methoddann nicht an die gewünschte Basis- "Klasse" gebunden ist und daher keine nicht überschriebene Instanzmethode / Getter ist . Auf diese Weise ist es immer möglich, mit klassenunabhängigen Methoden zu arbeiten. Trotzdem denke ich nicht, dass das aktuelle Design so schlecht ist. Im Kontext von Klassenobjekten, die von Ihrer Basisklasse Foo abgeleitet sind, kann es gute Gründe geben, eine Instanzmethode zu überschreiben. Diese Überschreibungsmethode kann gute Gründe haben, ihre superImplementierung aufzurufen oder nicht. Jeder Fall ist förderfähig und sollte befolgt werden. Andernfalls würde es zu einem schlechten OOP-Design führen.
Thomas Urban
Trotz des OOP-Zuckers sind ES-Methoden immer noch Funktionen , und die Leute werden sie als solche verwenden und referenzieren wollen. Mein Problem mit der ES-Klassensyntax ist, dass sie keinen direkten Verweis auf die aktuell ausgeführte Methode bietet - etwas, das früher einfach über arguments.calleeoder eine NFE war.
Roy Tinker
Klingt nach schlechter Praxis oder zumindest nach schlechtem Software-Design. Ich würde beide Standpunkte als gegensätzlich betrachten, da ich im Kontext des OOP-Paradigmas, das den Zugriff auf die aktuell aufgerufene Methode durch Referenz beinhaltet (was nicht nur der Kontext ist, über den über verfügbar ist this), keinen geeigneten Grund sehe . Es klingt wie der Versuch, die Vorteile der Zeigerarithmetik von nacktem C mit höherwertigem C # zu mischen. Nur aus Neugier: Wofür würden Sie arguments.calleesauber gestalteten OOP-Code verwenden?
Thomas Urban
Ich arbeite in einem großen Projekt, das mit dem Klassensystem von Dojo erstellt wurde und das Aufrufen der Implementierung (en) der Oberklasse (n) der aktuellen Methode über this.inherited(currentFn, arguments);- wobei currentFnein Verweis auf die aktuell ausgeführte Funktion ist. Wenn die aktuell ausgeführte Funktion nicht direkt referenziert werden kann, ist sie in TypeScript, das seine Klassensyntax aus ES6 übernimmt, etwas haarig.
Roy Tinker
20

Wenn Sie irgendeine Art von Vererbung planen, würde ich empfehlen this.constructor. Dieses einfache Beispiel sollte veranschaulichen, warum:

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()wird sich ConstructorSuper Hello ConstructorSuper!an der Konsole anmelden
  • test2.callPrint()wird sich ConstructorSub Hello ConstructorSub!an der Konsole anmelden

Die benannte Klasse wird die Vererbung nur dann gut behandeln, wenn Sie jede Funktion, die auf die benannte Klasse verweist, explizit neu definieren. Hier ist ein Beispiel:

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()wird sich NamedSuper Hello NamedSuper!an der Konsole anmelden
  • test4.callPrint()wird sich NamedSuper Hello NamedSub!an der Konsole anmelden

Sehen Sie alle oben genannten in Babel REPL .

Sie können daran erkennen, test4dass es immer noch in der Superklasse ist; In diesem Beispiel scheint es keine große Sache zu sein, aber wenn Sie versuchen, überschriebene Elementfunktionen oder neue Elementvariablen zu referenzieren, werden Sie in Schwierigkeiten geraten.

Andrew Odri
quelle
3
Aber statische Funktionen sind keine überschriebenen Mitgliedsmethoden? Normalerweise versuchen Sie nicht jede überschriebene Sachen statisch zu verweisen.
Bergi
1
@Bergi Ich bin mir nicht sicher, ob ich verstehe, worauf Sie hinweisen, aber ein spezieller Fall, auf den ich gestoßen bin, sind die Hydratationsmuster des MVC-Modells. Unterklassen, die ein Modell erweitern, möchten möglicherweise eine statische Hydratfunktion implementieren. Wenn diese jedoch fest codiert sind, werden Basismodellinstanzen immer nur zurückgegeben. Dies ist ein ziemlich spezifisches Beispiel, aber viele Muster, die auf einer statischen Sammlung registrierter Instanzen beruhen, würden dadurch beeinflusst. Ein großer Haftungsausschluss ist, dass wir hier versuchen, die klassische Vererbung zu simulieren, anstatt die prototypische Vererbung ... Und das ist nicht beliebt: P
Andrew Odri
Ja, wie ich jetzt in meiner eigenen Antwort festgestellt habe, wird dies in der "klassischen" Vererbung nicht einmal konsequent gelöst - manchmal möchten Sie vielleicht Überschreibungen, manchmal nicht. Der erste Teil meines Kommentars bezog sich auf statische Klassenfunktionen, die ich nicht als "Mitglieder" betrachtete. Ignoriere es besser :-)
Bergi