Chiama i metodi statici dai normali metodi di classe ES6


176

Qual è il modo standard di chiamare metodi statici? Posso pensare di usareconstructor o usare il nome della classe stessa, non mi piace quest'ultima poiché non mi sembra necessaria. Il primo è il modo raccomandato o c'è qualcos'altro?

Ecco un esempio (inventato):

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

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

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

8
SomeObject.printsembra naturale. Ma this.ndentro non ha senso poiché non esiste un esempio, se stiamo parlando di metodi statici.
dfsq,

3
@dfsq printNnon è statico però.
simonzack,

Hai ragione, nomi confusi.
dfsq,

1
Sono curioso di sapere perché questa domanda non ha tanti voti positivi! Non è una pratica comune per creare funzioni di utilità?
Thoran,

Risposte:


211

Entrambe le vie sono praticabili, ma fanno cose diverse quando si tratta di ereditarietà con un metodo statico ignorato. Scegli quello di cui ti aspetti il ​​comportamento:

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

Il riferimento alla proprietà statica tramite la classe sarà effettivamente statico e fornirà costantemente lo stesso valore. L'uso di this.constructorinvece utilizzerà l'invio dinamico e farà riferimento alla classe dell'istanza corrente, dove potrebbe essere la proprietà statica avere il valore ereditato ma potrebbe anche essere sovrascritta.

Ciò corrisponde al comportamento di Python, in cui è possibile scegliere di fare riferimento a proprietà statiche tramite il nome della classe o l'istanza self.

Se ti aspetti che le proprietà statiche non vengano sovrascritte (e fai sempre riferimento a quella della classe corrente), come in Java , usa il riferimento esplicito.


puoi spiegare la proprietà del costruttore rispetto alla definizione del metodo di classe?
Chris,

2
@Chris: ogni classe è una funzione di costruzione (proprio come la conosci da ES5 senza classsintassi), non c'è differenza nella definizione del metodo. È solo una questione di come la cerchi, tramite la constructorproprietà ereditata o direttamente con il suo nome.
Bergi,

Un altro esempio sono i binding statici tardivi di PHP . Questo.costruttore non solo rispetta l'eredità, ma aiuta anche a evitare di dover aggiornare il codice se si modifica il nome della classe.
ricanontherun,

@ricanontherun Dover aggiornare il codice quando si cambiano i nomi delle variabili non è un argomento contro l'utilizzo dei nomi. Anche gli strumenti di refactoring possono farlo automaticamente comunque.
Bergi,

Come implementarlo in dattiloscritto? Dà l'erroreProperty 'staticProperty' does not exist on type 'Function'
ayZagen

72

Mi sono imbattuto in questo thread alla ricerca di una risposta a un caso simile. Fondamentalmente si trovano tutte le risposte, ma è ancora difficile estrarre da esse gli elementi essenziali.

Tipi di accesso

Assumi una classe Foo probabilmente derivata da alcune altre classi con probabilmente più classi derivate da essa.

Quindi accedendo

  • dal metodo statico / getter di Foo
    • alcuni metodi / getter statici probabilmente sovrascritti :
      • this.method()
      • this.property
    • qualche metodo / getter di istanza probabilmente sovrascritto :
      • impossibile dal design
    • proprio metodo statico / getter non sovrascritto :
      • Foo.method()
      • Foo.property
    • proprio metodo / getter di istanza non sovrascritto :
      • impossibile dal design
  • dal metodo di istanza / getter di Foo
    • alcuni metodi / getter statici probabilmente sovrascritti :
      • this.constructor.method()
      • this.constructor.property
    • qualche metodo / getter di istanza probabilmente sovrascritto :
      • this.method()
      • this.property
    • proprio metodo statico / getter non sovrascritto :
      • Foo.method()
      • Foo.property
    • proprio metodo / getter di istanza non sovrascritto :
      • non possibile intenzionalmente a meno che non si utilizzi qualche soluzione alternativa :
        • Foo.prototype.method.call( this )
        • Object.getOwnPropertyDescriptor( Foo.prototype,"property" ).get.call(this);

Tenere presente che l'utilizzo thisnon funziona in questo modo quando si utilizzano le funzioni freccia o si invocano metodi / getter esplicitamente associati al valore personalizzato.

sfondo

  • Nel contesto del metodo o getter di un'istanza
    • this si riferisce all'istanza corrente.
    • super si riferisce sostanzialmente alla stessa istanza, ma si sta estendendo in qualche modo metodi e getter scritti nel contesto di una classe attuale (usando il prototipo del prototipo di Foo).
    • la definizione della classe dell'istanza utilizzata per crearla è disponibile per this.constructor.
  • Quando nel contesto di un metodo statico o getter non esiste "istanza corrente" per intenzione e così
    • this è disponibile per fare riferimento direttamente alla definizione della classe corrente.
    • super non si riferisce nemmeno a qualche istanza, ma si sta estendendo a metodi statici e getter scritti nel contesto di una classe corrente.

Conclusione

Prova questo codice:

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--- È un vero peccato. Secondo me, questo è un difetto di ES6 +. Forse dovrebbe essere aggiornato per consentire semplicemente di fare riferimento a method- ad es method.call(this). Meglio di Foo.prototype.method. Babel / etc. potrebbe implementare usando un NFE (chiamato espressione di funzione).
Roy Tinker,

method.call( this )è una soluzione probabile, tranne per il fatto che methodnon è associato alla "classe" di base desiderata e quindi non è un metodo / getter di istanza non scavalcato . In questo modo è sempre possibile lavorare con metodi indipendenti dalla classe. Tuttavia, non credo che l'attuale design sia così negativo. Nel contesto di oggetti di classe derivati ​​dalla tua classe di base Foo potrebbero esserci buone ragioni per sovrascrivere un metodo di istanza. Tale metodo scavalcato può avere buoni motivi per invocare la sua superattuazione o meno. Entrambi i casi sono ammissibili e devono essere rispettati. Altrimenti finirebbe con un cattivo design OOP.
Thomas Urban,

Nonostante lo zucchero OOP, i metodi ES sono ancora funzioni e le persone vorranno utilizzarli e fare riferimento a loro in quanto tali. Il mio problema con la sintassi della classe ES è che non fornisce alcun riferimento diretto al metodo attualmente in esecuzione - qualcosa che era facile tramitearguments.callee o un NFE.
Roy Tinker,

Sembra comunque una cattiva pratica o almeno una cattiva progettazione del software. Considererei entrambi i punti di vista opposti l'uno all'altro, poiché non vedo la ragione ammissibile nel contesto del paradigma OOP che prevede l'accesso al metodo attualmente invocato per riferimento (che non è solo il suo contesto come è disponibile tramite this). Sembra provare a mescolare i vantaggi dell'aritmetica del puntatore di C nudo con C # di livello superiore. Solo per curiosità: cosa arguments.calleeuseresti per un codice OOP progettato in modo pulito?
Thomas Urban,

Sto lavorando a un grande progetto costruito con il sistema di classi Dojo, che consente di chiamare l'implementazione / le superclasse del metodo corrente tramite this.inherited(currentFn, arguments);- dove currentFnè un riferimento alla funzione attualmente in esecuzione. Non essere in grado di fare riferimento direttamente alla funzione attualmente in esecuzione significa renderla un po 'pelosa in TypeScript, che prende la sua sintassi di classe da ES6.
Roy Tinker,

20

Se hai intenzione di fare qualsiasi tipo di eredità, allora consiglierei this.constructor. Questo semplice esempio dovrebbe illustrare perché:

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()accederà ConstructorSuper Hello ConstructorSuper!alla console
  • test2.callPrint()accederà ConstructorSub Hello ConstructorSub!alla console

La classe nominata non tratterà bene l'ereditarietà a meno che non si ridefinisca esplicitamente ogni funzione che fa riferimento alla classe denominata. Ecco un esempio:

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()accederà NamedSuper Hello NamedSuper!alla console
  • test4.callPrint()accederà NamedSuper Hello NamedSub!alla console

Vedi tutto quanto sopra in esecuzione in Babel REPL .

Puoi vedere da questo che test4 pensa ancora che sia nella super classe; in questo esempio potrebbe non sembrare un grosso problema, ma se stai cercando di fare riferimento a funzioni membro che sono state sostituite o nuove variabili membro, ti troverai nei guai.


3
Ma le funzioni statiche non sono metodi membri sostituiti? Di solito stai cercando di non fare riferimento staticamente a qualsiasi roba sostituita.
Bergi,

1
@Bergi Non sono sicuro di aver capito cosa stai sottolineando, ma un caso specifico che ho riscontrato sarebbe con i modelli di idratazione del modello MVC. Le sottoclassi che estendono un modello potrebbero voler implementare una funzione di idratazione statica. Tuttavia, quando questi sono codificati, le istanze del modello di base vengono sempre restituite. Questo è un esempio piuttosto specifico, ma molti modelli che si basano sull'avere una raccolta statica di istanze registrate verrebbero influenzati da questo. Un grande disclaimer è che qui stiamo cercando di simulare l'eredità classica, piuttosto che l'eredità prototipale ... E questo non è popolare: P
Andrew Odri,

Sì, come ho concluso ora nella mia risposta, questo non è nemmeno risolto in modo coerente nell'eredità "classica" - a volte potresti voler scavalcare, a volte no. La prima parte del mio commento puntava su funzioni di classe statiche, che non consideravo "membri". Meglio ignorarlo :-)
Bergi,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.