Appeler des méthodes statiques à partir de méthodes de classe ES6 régulières

176

Quelle est la manière standard d'appeler des méthodes statiques? Je peux penser à utiliser constructorou utiliser le nom de la classe elle-même, je n'aime pas ce dernier car cela ne me semble pas nécessaire. La première méthode est-elle recommandée ou y a-t-il autre chose?

Voici un exemple (artificiel):

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

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

  printN(){
    this.constructor.print(this.n);
  }
}
Simonzack
la source
8
SomeObject.printsemble naturel. Mais à l' this.nintérieur n'a aucun sens puisqu'il n'y a pas d'instance, si nous parlons de méthodes statiques.
dfsq
3
@dfsq printNn'est cependant pas statique.
simonzack
Vous avez raison, noms confus.
dfsq
1
Je suis curieux de savoir pourquoi cette question n'a pas autant de votes positifs! N'est-ce pas une pratique courante pour créer des fonctions utilitaires?
Thoran

Réponses:

211

Les deux méthodes sont viables, mais elles font des choses différentes lorsqu'il s'agit d'héritage avec une méthode statique remplacée. Choisissez celui dont vous attendez le comportement:

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

Faire référence à la propriété static via la classe sera en fait statique et donnera constamment la même valeur. Utiliser à la this.constructorplace utilisera la répartition dynamique et fera référence à la classe de l'instance actuelle, où la propriété statique peut avoir la valeur héritée mais peut également être remplacée.

Cela correspond au comportement de Python, où vous pouvez choisir de faire référence à des propriétés statiques via le nom de la classe ou l'instance self.

Si vous vous attendez à ce que les propriétés statiques ne soient pas remplacées (et faites toujours référence à celle de la classe actuelle), comme en Java , utilisez la référence explicite.

Bergi
la source
pouvez-vous expliquer la propriété du constructeur par rapport à la définition de la méthode de classe?
Chris
2
@Chris: Chaque classe est une fonction constructeur (comme vous la connaissez depuis ES5 sans classsyntaxe), il n'y a aucune différence dans la définition de la méthode. C'est juste une question de savoir comment vous le recherchez, via la constructorpropriété héritée ou directement par son nom.
Bergi
Les liaisons statiques tardives de PHP sont un autre exemple . Non seulement this.constructor respecte l'héritage, mais vous évite également d'avoir à mettre à jour le code si vous modifiez le nom de la classe.
ricanontherun
@ricanontherun Le fait de devoir mettre à jour le code lorsque vous changez les noms de variables n'est pas un argument contre l'utilisation de noms. De même, les outils de refactoring peuvent le faire automatiquement.
Bergi
Comment implémenter cela en tapuscrit? Il donne l'erreurProperty 'staticProperty' does not exist on type 'Function'
ayZagen
72

Je suis tombé sur ce fil à la recherche d'une réponse à un cas similaire. En gros, toutes les réponses sont trouvées, mais il est encore difficile d'en extraire l'essentiel.

Types d'accès

Supposons qu'une classe Foo soit probablement dérivée d'une ou plusieurs autres classes avec probablement plus de classes dérivées.

Puis accéder

  • à partir de la méthode statique / getter de Foo
    • une méthode statique / getter probablement surchargée :
      • this.method()
      • this.property
    • une méthode d' instance / getter probablement surchargée :
      • impossible par conception
    • propre méthode statique / getter non surchargée:
      • Foo.method()
      • Foo.property
    • propre méthode / getter d' instance non surchargée:
      • impossible par conception
  • à partir de la méthode d' instance / getter de Foo
    • une méthode statique / getter probablement surchargée :
      • this.constructor.method()
      • this.constructor.property
    • une méthode d' instance / getter probablement surchargée :
      • this.method()
      • this.property
    • propre méthode statique / getter non surchargée:
      • Foo.method()
      • Foo.property
    • propre méthode / getter d' instance non surchargée:
      • pas possible par intention à moins d'utiliser une solution de contournement :
        • Foo.prototype.method.call( this )
        • Object.getOwnPropertyDescriptor( Foo.prototype,"property" ).get.call(this);

Gardez à l'esprit que l'utilisation thisne fonctionne pas de cette façon lors de l'utilisation de fonctions fléchées ou de l'appel de méthodes / accesseurs explicitement liés à une valeur personnalisée.

Contexte

  • Dans le contexte de la méthode ou du getter d'une instance
    • this fait référence à l'instance actuelle.
    • super fait essentiellement référence à la même instance, mais un peu les méthodes d'adressage et les getters écrits dans le contexte d'une classe actuelle s'étend (en utilisant le prototype du prototype de Foo).
    • la définition de la classe de l'instance utilisée lors de sa création est disponible par this.constructor.
  • Lorsque dans le contexte d'une méthode statique ou d'un getter, il n'y a pas d '"instance actuelle" par intention et donc
    • this est disponible pour se référer directement à la définition de la classe courante.
    • super ne fait pas non plus référence à une instance, mais à des méthodes statiques et des getters écrits dans le contexte d'une classe actuelle.

Conclusion

Essayez ce 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 Urbain
la source
1
Own non-overridden instance method/getter / not possible by intention unless using some workaround--- C'est vraiment dommage. À mon avis, c'est une lacune de ES6 +. Peut-être qu'il devrait être mis à jour pour permettre simplement de faire référence à method- c'est- à -dire method.call(this). Mieux que Foo.prototype.method. Babel / etc. pourrait implémenter en utilisant une NFE (expression de fonction nommée).
Roy Tinker
method.call( this )est une solution probable sauf que le methodn'est pas lié à la «classe» de base souhaitée et ne peut donc pas être une méthode / getter d'instance non surchargée . Il est toujours possible de travailler avec des méthodes indépendantes de la classe de cette façon. Néanmoins, je ne pense pas que la conception actuelle soit si mauvaise. Dans le contexte des objets de classe dérivés de votre classe de base Foo, il peut y avoir de bonnes raisons de remplacer une méthode d'instance. Cette méthode surchargée peut avoir de bonnes raisons d'invoquer superou non son implémentation. Les deux cas sont éligibles et doivent être respectés. Sinon, cela se terminerait par une mauvaise conception de la POO.
Thomas Urban
Malgré le sucre POO, les méthodes ES sont toujours fonctionnelles et les gens voudront les utiliser et les référencer comme telles. Mon problème avec la syntaxe de classe ES est qu'elle ne fournit aucune référence directe à la méthode en cours d'exécution - quelque chose qui était auparavant facile via arguments.calleeou un NFE.
Roy Tinker
Cela ressemble à une mauvaise pratique ou au moins à une mauvaise conception de logiciel. Je considérerais les deux points de vue comme contraires l'un à l'autre, car je ne vois pas de raison éligible dans le contexte du paradigme OOP qui implique d'accéder à la méthode actuellement invoquée par référence (qui n'est pas seulement son contexte tel qu'il est disponible via this). Cela ressemble à essayer de mélanger les avantages de l'arithmétique des pointeurs de C nu avec C # de plus haut niveau. Juste par curiosité: à quoi utiliseriez-vous arguments.calleedans un code POO proprement conçu?
Thomas Urban
Je travaille dans un grand projet construit avec le système de classes de Dojo, qui permet d'appeler la ou les implémentations de superclasse (s) de la méthode actuelle via this.inherited(currentFn, arguments);- où currentFnest une référence à la fonction en cours d'exécution. Ne pas pouvoir référencer directement la fonction en cours d'exécution la rend un peu poilue dans TypeScript, qui tire sa syntaxe de classe d'ES6.
Roy Tinker
20

Si vous prévoyez de faire n'importe quel type d'héritage, je vous le recommande this.constructor. Cet exemple simple devrait illustrer pourquoi:

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

La classe nommée ne traitera pas correctement l'héritage à moins que vous ne redéfinissiez explicitement chaque fonction qui fait une référence à la classe nommée. Voici un exemple:

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

Voir tout ce qui précède en cours d'exécution dans Babel REPL .

Vous pouvez voir de cela qui test4pense toujours que c'est dans la super classe; dans cet exemple, cela peut ne pas sembler un gros problème, mais si vous essayez de référencer des fonctions membres qui ont été remplacées ou de nouvelles variables membres, vous vous retrouverez en difficulté.

Andrew Odri
la source
3
Mais les fonctions statiques ne sont-elles pas des méthodes membres remplacées? Habituellement, vous essayez de ne référencer statiquement aucun élément surchargé.
Bergi
1
@Bergi Je ne suis pas sûr de comprendre ce que vous faites remarquer, mais un cas spécifique que j'ai rencontré serait celui des modèles d'hydratation MVC. Les sous-classes étendant un modèle peuvent vouloir implémenter une fonction hydrate statique. Cependant, lorsqu'ils sont codés en dur, les instances du modèle de base sont uniquement renvoyées. Il s'agit d'un exemple assez spécifique, mais de nombreux modèles qui reposent sur une collection statique d'instances enregistrées seraient affectés par cela. Un gros avertissement est que nous essayons de simuler l'héritage classique ici, plutôt que l'héritage prototypique ... Et ce n'est pas populaire: P
Andrew Odri
Ouais, comme je l'ai conclu maintenant dans ma propre réponse, cela n'est même pas résolu de manière cohérente dans l'héritage "classique" - parfois vous voudrez peut-être des remplacements, parfois non. La première partie de mon commentaire portait sur les fonctions de classe statiques, que je ne considérais pas comme des «membres». Mieux vaut l'ignorer :-)
Bergi