¿Cómo hago un "campo estático público" en una clase ES6?

86

Estoy haciendo una clase de Javascript y me gustaría tener un campo estático público como en Java. Este es el código relevante:

export default class Agent {
    CIRCLE: 1,
    SQUARE: 2,
    ...

Este es el error que obtengo:

line 2, col 11, Class properties must be methods. Expected '(' but instead saw ':'.

Parece que los módulos ES6 no permiten esto. ¿Hay alguna manera de obtener el comportamiento deseado o tengo que escribir un captador?

Aebabis
fuente
¿Qué implementación del motor ECMAScript 6 está utilizando?
Dai

Respuestas:

136

Usted crea un "campo estático público" usando un descriptor de acceso y una palabra clave "estática":

class Agent {
    static get CIRCLE() {
      return 1;
    }
    static get SQUARE() {
      return 2;
    }
}

Agent.CIRCLE; // 1

Mirando una especificación, 14.5 - Definiciones de clase - verías algo sospechosamente relevante :)

ClassElement [Yield]:
  MethodDefinition [? Yield]
  static MethodDefinition [? Yield];

Entonces, desde allí, puede seguir a 14.5.14 - Runtime Semantics: ClassDefinitionEvaluation - para verificar si realmente hace lo que parece. Específicamente, paso 20:

  1. Para cada ClassElement m en orden de métodos
    1. Si IsStatic de m es falso , entonces
      1. Sea status el resultado de realizar PropertyDefinitionEvaluation para m con argumentos proto y false.
    2. Más,
      1. Sea status el resultado de realizar PropertyDefinitionEvaluation para m con argumentos F y falso.
    3. Si el estado es una terminación abrupta, entonces
      1. Establezca LexicalEnvironment del contexto de ejecución en ejecución en lex.
      2. Estado de devolución.

IsStatic se define anteriormente en 14.5.9

ClassElement: static MethodDefinition
Devuelve verdadero.

Así PropertyMethodDefinitionse llama con "F" (constructor, objeto de función) como argumento, que a su vez crea un método de acceso en ese objeto .

Esto ya funciona en al menos IETP (vista previa técnica), así como en los compiladores 6to5 y Traceur.

Kangax
fuente
Para cualquier otra persona que busque, las propiedades de acceso estático aún no son compatibles con Node. : - / kangax.github.io/compat-table/es6/…
David Hernandez
1
A partir de al menos Node.js 6.x +, esto es compatible.
NuSkooler
Tenga en cuenta que si está utilizando el flujo, debe agregar una línea unsafe.enable_getters_and_setters=truea su .flowconfig debajo [options](lo cual es molesto).
kristina
Esto no funcionará para mí. Estoy obteniendo `` Rechazo no controlado TypeError: No se puede establecer la propiedad dataHashKey de la clase Colecciones {api_1 | static get dataHashKey () {api_1 | devolver 'colecciones'; api_1 | } `` `
Pavan
54

Existe una propuesta de ECMAScript de etapa 3 llamada "Características de clase estática" de Daniel Ehrenberg y Jeff Morrison que tiene como objetivo resolver este problema. Junto con la propuesta de "Campos de clase" de la etapa 3 , el código futuro se verá así:

class MyClass {
    static myStaticProp = 42;
    myProp = 42;
    myProp2 = this.myProp;
    myBoundFunc = () => { console.log(this.myProp); };

    constructor() {
        console.log(MyClass.myStaticProp); // Prints '42'
        console.log(this.myProp); // Prints '42'
        this.myBoundFunc(); // Prints '42'
    }
}

Lo anterior es equivalente a:

class MyClass {
    constructor() {
        this.myProp = 42;
        this.myProp2 = this.myProp;
        this.myBoundFunc = () => { console.log(this.myProp); };

        console.log(MyClass.myStaticProp); // Prints '42'
        console.log(this.myProp); // Prints '42'
        this.myBoundFunc(); // Prints '42'
    }
}
MyClass.myStaticProp = 42;

Babel admite la transpiración de campos de clase a través de @ babel / plugin-proposition-class-properties (incluido en el ajuste preestablecido de la etapa 3 ), de modo que puede usar esta función incluso si su tiempo de ejecución de JavaScript no la admite.


En comparación con la solución de @ kangax de declarar un getter, esta solución también puede ser más eficaz, ya que aquí se accede a la propiedad directamente en lugar de llamar a una función.

Si se acepta esta propuesta, entonces será posible escribir código JavaScript de una manera más similar a los lenguajes tradicionales orientados a objetos como Java y C♯.


Editar : una propuesta de campos de clase unificada se encuentra ahora en la etapa 3; actualice a los paquetes de Babel v7.x.

Editar (febrero de 2020) : las características de la clase estática se han dividido en una propuesta diferente. ¡Gracias @ GOTO0!

Timothy Gu
fuente
Creo que la propuesta relevante es en realidad esta ( características de clase estática ).
GOTO 0
29

En los borradores actuales de ECMAScript 6 (a febrero de 2015), todas las propiedades de clase deben ser métodos, no valores (tenga en cuenta que en ECMAScript una "propiedad" es similar en concepto a un campo OOP, excepto que el valor del campo debe ser un Functionobjeto, no cualquier otro valor como a Numbero Object).

Aún puede especificarlos utilizando los especificadores de propiedad del constructor ECMAScript tradicionales:

 class Agent {
 }
 Agent.CIRCLE = 1;
 Agent.SQUARE = 2;
 ...
Dai
fuente
11
Tenga en cuenta que la classsintaxis de ES6 es simplemente azúcar sintáctica para las funciones y prototipos de constructores JS tradicionales de todos modos.
Matt Browne
Creo que querrá poner esas propiedades en el prototipo y no en el constructor para que sean visibles a través de referencias de propiedad de instancias.
Puntiagudo
@Pointy Inferí que el OP está tratando de almacenar constantes como referencia (casi como un C # /. NET enum).
Dai
2
@MattBrowne Sí, pero para ser claros, la classsintaxis también tiene ciertas diferencias matizadas. Por ejemplo, un método declarado con Class.prototype.method = function () {};es enumerable (visible con bucles for-in), mientras que los classmétodos no son enumerables.
Timothy Gu
4

Para aprovechar al máximo la variable estática, seguí este enfoque. Para ser más específicos, podemos usarlo para usar una variable privada o tener solo un getter público, o tener ambos getter o setter. En el último caso, es igual a una de las soluciones publicadas anteriormente.

var Url = (() => {
    let _staticMember = [];
    return class {
        static getQueries(hash = document.location.hash) {
            return hash;
        }

        static get staticMember(){
            return _staticMember;
        }
    };
})();

Usages:
console.log(Url.staticMember); // [];
Url.staticMember.push('it works');
console.log(Url.staticMember); // ['it works'];

Podría crear otra clase ampliando la URL y funcionó.

Usé babel para convertir mi código ES6 a ES5

SM Adnan
fuente
1
¿Qué es la "ventaja total"? ¿No class Url { static getQueries… }; Url.staticMember = [];habría sido mucho más sencillo?
Bergi
Esas ===comparaciones tanto el rendimiento false, por cierto
Bergi
"Full Advantage" significa que, de la forma anterior, puede mantener _staticMember como privado, si lo desea.
SM Adnan
-1

La respuesta de @kangax no imita todo el comportamiento estático de los lenguajes OOP tradicionales, porque no puede acceder a la propiedad estática por su instancia como const agent = new Agent; agent.CIRCLE; // Undefined

Si desea acceder a una propiedad estática como OOP, aquí está mi solución:

class NewApp {
  get MULTIPLE_VERSIONS_SUPPORTED() {
    return this.constructor.MULTIPLE_VERSIONS_SUPPORTED; // Late binding for inheritance
  }
}

NewApp.MULTIPLE_VERSIONS_SUPPORTED = true;

Pruebe el código de la siguiente manera.

class NewApp {
  get MULTIPLE_VERSIONS_SUPPORTED() {
    console.log('this.constructor.name:', this.constructor.name); // late binding
    return this.constructor.MULTIPLE_VERSIONS_SUPPORTED;
  }
}

// Static property can be accessed by class
NewApp.MULTIPLE_VERSIONS_SUPPORTED = true;

const newApp = new NewApp;

// Static property can be accessed by it's instances
console.log('newApp.MULTIPLE_VERSIONS_SUPPORTED:', newApp.MULTIPLE_VERSIONS_SUPPORTED); // true

// Inheritance
class StandardApp extends NewApp {}

// Static property can be inherited
console.log('StandardApp.MULTIPLE_VERSIONS_SUPPORTED:', StandardApp.MULTIPLE_VERSIONS_SUPPORTED); // true

// Static property can be overwritten
StandardApp.MULTIPLE_VERSIONS_SUPPORTED = false;

const std = new StandardApp;

console.log('std.MULTIPLE_VERSIONS_SUPPORTED:', std.MULTIPLE_VERSIONS_SUPPORTED); // false

leyenda de los 80
fuente
1
Acceder a un staticcampo mediante una instancia sería bastante poco común, ¿no crees? En algunos lenguajes, como Java, los IDE emiten una advertencia / pista si haces algo así.
Isac
@Isac Sí, tienes razón. Se desaconseja el acceso por instancia y también mi respuesta. Solo otra perspectiva de la solución. 😀
legend80s