Llamar a métodos estáticos desde métodos de clase ES6 normales

175

¿Cuál es la forma estándar de llamar a métodos estáticos? Puedo pensar en usar constructoro usar el nombre de la clase en sí, no me gusta este último ya que no me parece necesario. ¿Es la primera la forma recomendada o hay algo más?

Aquí hay un ejemplo (artificial):

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

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

  printN(){
    this.constructor.print(this.n);
  }
}
simonzack
fuente
8
SomeObject.printse siente natural Pero por this.ndentro no tiene sentido ya que no hay instancia, si estamos hablando de métodos estáticos.
dfsq
3
Sin printNembargo, @dfsq no es estático.
simonzack
Tienes razón, nombres confusos.
dfsq
1
¡Tengo curiosidad por qué esta pregunta no tiene tantos votos positivos! ¿No es esta una práctica común para crear funciones de utilidad?
Thoran

Respuestas:

210

Ambas formas son viables, pero hacen cosas diferentes cuando se trata de herencia con un método estático anulado. Elija el comportamiento que espera:

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

Hacer referencia a la propiedad estática a través de la clase será realmente estática y constantemente dará el mismo valor. En su this.constructorlugar, usará el despacho dinámico y se referirá a la clase de la instancia actual, donde la propiedad estática podría tener el valor heredado pero también podría anularse.

Esto coincide con el comportamiento de Python, donde puede elegir hacer referencia a propiedades estáticas, ya sea a través del nombre de la clase o la instancia self.

Si espera que las propiedades estáticas no se anulen (y siempre se refieren a la de la clase actual), como en Java , use la referencia explícita.

Bergi
fuente
¿Puedes explicar la propiedad del constructor frente a la definición del método de clase?
Chris
2
@Chris: cada clase es una función constructora (tal como la conoce desde ES5 sin classsintaxis), no hay diferencia en la definición del método. Es solo una cuestión de cómo buscarlo, a través de la constructorpropiedad heredada o directamente por su nombre.
Bergi
Otro ejemplo son los enlaces estáticos tardíos de PHP . This.constructor no solo respeta la herencia, sino que también le ayuda a evitar tener que actualizar el código si cambia el nombre de la clase.
ricanontherun
@ricanontherun Tener que actualizar el código cuando cambia los nombres de las variables no es un argumento en contra del uso de nombres. También las herramientas de refactorización pueden hacerlo automáticamente de todos modos.
Bergi
¿Cómo implementar esto en mecanografiado? Da el errorProperty 'staticProperty' does not exist on type 'Function'
ayZagen
71

Me topé con este hilo en busca de una respuesta a un caso similar. Básicamente se encuentran todas las respuestas, pero aún es difícil extraer lo esencial de ellas.

Tipos de acceso

Suponga que una clase Foo probablemente deriva de alguna otra clase (s) con probablemente más clases derivadas de ella.

Luego accediendo

  • del método estático / captador de Foo
    • algunos métodos estáticos / getter probablemente anulados :
      • this.method()
      • this.property
    • algún método / getter de instancia probablemente anulado :
      • imposible por diseño
    • propio método / captador estático no anulado :
      • Foo.method()
      • Foo.property
    • propio método / getter de instancia no anulada :
      • imposible por diseño
  • del método de instancia / getter de Foo
    • algunos métodos estáticos / getter probablemente anulados :
      • this.constructor.method()
      • this.constructor.property
    • algún método / getter de instancia probablemente anulado :
      • this.method()
      • this.property
    • propio método / captador estático no anulado :
      • Foo.method()
      • Foo.property
    • propio método / getter de instancia no anulada :
      • no es posible por intención a menos que use alguna solución alternativa :
        • Foo.prototype.method.call( this )
        • Object.getOwnPropertyDescriptor( Foo.prototype,"property" ).get.call(this);

Tenga en cuenta que usar thisno funciona de esta manera al usar funciones de flecha o al invocar métodos / captadores vinculados explícitamente a un valor personalizado.

Antecedentes

  • Cuando en el contexto del método o getter de una instancia
    • this se refiere a la instancia actual.
    • super Básicamente se refiere a la misma instancia, pero de alguna manera se está ampliando los métodos y captadores escritos en el contexto de alguna clase actual (mediante el uso del prototipo del prototipo de Foo).
    • La definición de la clase de instancia utilizada para crearla está disponible por this.constructor.
  • Cuando en el contexto de un método estático o getter no hay una "instancia actual" por intención y así
    • this está disponible para referirse directamente a la definición de clase actual.
    • super tampoco se refiere a alguna instancia, sino a métodos estáticos y captadores escritos en el contexto de alguna clase actual que se está extendiendo.

Conclusión

Prueba este código:

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
fuente
1
Own non-overridden instance method/getter / not possible by intention unless using some workaround--- Es una verdadera pena. En mi opinión, esta es una deficiencia de ES6 +. Tal vez debería actualizarse para permitir simplemente referirse a method, es decir method.call(this). Mejor que Foo.prototype.method. Babel / etc. podría implementarse usando una NFE (expresión de función con nombre).
Roy Tinker
method.call( this )es una solución probable, excepto que methodno está vinculada a la "clase" base deseada en ese momento y, por lo tanto, no puede ser un método / getter de instancia no anulado . Siempre es posible trabajar con métodos independientes de la clase de esa manera. Sin embargo, no creo que el diseño actual sea tan malo. En el contexto de los objetos de clase derivados de su clase base Foo, puede haber buenas razones para anular un método de instancia. Ese método anulado puede tener buenas razones para invocar su superimplementación o no. Cualquiera de los casos es elegible y debe ser obedecido. De lo contrario, terminaría en un mal diseño de OOP.
Thomas Urban
A pesar del azúcar OOP, los métodos ES siguen siendo funciones , y la gente querrá usarlos y hacer referencia a ellos como tales. Mi problema con la sintaxis de la clase ES es que no proporciona una referencia directa al método que se está ejecutando actualmente, algo que solía ser fácil a través de arguments.calleeuna NFE.
Roy Tinker
Suena como una mala práctica o al menos un mal diseño de software de todos modos. Consideraría ambos puntos de vista opuestos entre sí, ya que no veo una razón elegible en el contexto del paradigma OOP que implica acceder al método invocado actualmente por referencia (que no es solo su contexto como está disponible a través de this). Suena como tratar de mezclar los beneficios de la aritmética de puntero de C desnuda con C # de nivel superior. Solo por curiosidad: ¿para qué arguments.calleeusarías en un código OOP de diseño limpio?
Thomas Urban
Estoy trabajando en un gran proyecto construido con el sistema de clases de Dojo, que permite llamar a las implementaciones de superclase (s) del método actual a través de this.inherited(currentFn, arguments);- donde currentFnhay una referencia a la función que se está ejecutando actualmente. No poder hacer referencia directa a la función que se está ejecutando actualmente es un poco difícil en TypeScript, que toma su sintaxis de clase de ES6.
Roy Tinker
20

Si está planeando hacer algún tipo de herencia, entonces lo recomendaría this.constructor. Este simple ejemplo debería ilustrar por qué:

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()iniciará sesión ConstructorSuper Hello ConstructorSuper!en la consola
  • test2.callPrint()iniciará sesión ConstructorSub Hello ConstructorSub!en la consola

La clase nombrada no se ocupará muy bien de la herencia a menos que redefina explícitamente cada función que haga referencia a la Clase nombrada. Aquí hay un ejemplo:

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()iniciará sesión NamedSuper Hello NamedSuper!en la consola
  • test4.callPrint()iniciará sesión NamedSuper Hello NamedSub!en la consola

Ver todo lo anterior en ejecución en Babel REPL .

Puedes ver en esto que test4todavía piensa que está en la superclase; en este ejemplo, puede no parecer un gran problema, pero si está tratando de hacer referencia a funciones de miembros que han sido anuladas o nuevas variables de miembros, se encontrará en problemas.

Andrew Odri
fuente
3
¿Pero la función estática no son métodos de miembro anulados? Por lo general, está tratando de no hacer referencia a ningún elemento anulado estáticamente.
Bergi
1
@ Bergi No estoy seguro de entender lo que está señalando, pero un caso específico que he encontrado es con los patrones de hidratación del modelo MVC. Las subclases que extienden un modelo pueden querer implementar una función de hidrato estático. Sin embargo, cuando estos están codificados, las instancias del modelo base solo se devuelven. Este es un ejemplo bastante específico, pero esto afectaría a muchos patrones que dependen de tener una colección estática de instancias registradas. Un gran descargo de responsabilidad es que estamos tratando de simular la herencia clásica aquí, en lugar de la herencia prototípica ... Y eso no es popular: P
Andrew Odri
Sí, como concluí ahora en mi propia respuesta, esto ni siquiera se resuelve consistentemente en la herencia "clásica", a veces es posible que desee anulaciones, a veces no. La primera parte de mi comentario apuntaba a funciones de clase estáticas, que no consideraba "miembros". Mejor ignorarlo :-)
Bergi