Como faço um “campo estático público” em uma classe ES6?

86

Estou fazendo uma classe Javascript e gostaria de ter um campo estático público como em Java. Este é o código relevante:

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

Este é o erro que recebo:

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

Parece que os módulos ES6 não permitem isso. Existe uma maneira de obter o comportamento desejado ou tenho que escrever um getter?

aebabis
fonte
Qual implementação de mecanismo ECMAScript 6 você está usando?
Dai

Respostas:

136

Você torna "campo estático público" usando o acessador e uma palavra-chave "estática":

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

Agent.CIRCLE; // 1

Olhando para uma especificação, 14.5 - Definições de classe - você veria algo suspeitamente relevante :)

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

Então, de lá você pode seguir para 14.5.14 - Runtime Semantics: ClassDefinitionEvaluation - para verificar se ele realmente faz o que parece que faz. Especificamente, etapa 20:

  1. Para cada ClassElement m na ordem dos métodos
    1. Se IsStatic de m for falso , então
      1. Seja o status o resultado da execução de PropertyDefinitionEvaluation para m com os argumentos proto e false.
    2. Outro,
      1. Seja o status o resultado da execução de PropertyDefinitionEvaluation para m com os argumentos F e false.
    3. Se o status for uma conclusão abrupta, então
      1. Defina o LexicalEnvironment do contexto de execução em execução para lex.
      2. Status de retorno.

IsStatic é definido anteriormente em 14.5.9

ClassElement: static MethodDefinition
Retorna verdadeiro.

Portanto, PropertyMethodDefinitioné chamado com "F" (construtor, objeto de função) como um argumento, que por sua vez cria um método acessador nesse objeto .

Isso já funciona pelo menos em IETP (visualização técnica), bem como compiladores 6to5 e Traceur.

kangax
fonte
Para quem procura, as propriedades do acessador estático ainda não são suportadas no Node. : - / kangax.github.io/compat-table/es6/…
David Hernandez
1
Pelo menos a partir do Node.js 6.x +, isso é compatível.
NuSkooler
Observe que se você estiver usando o flow, terá que adicionar uma linha unsafe.enable_getters_and_setters=trueao seu .flowconfig em [options](o que é irritante).
Kristina
Isso não funcionará para mim, estou recebendo `` `Rejeição não tratada TypeError: Não é possível definir a propriedade dataHashKey das coleções de classe {api_1 | static get dataHashKey () {api_1 | retornar 'coleções'; api_1 | } `` `
Pavan
54

Existe uma proposta ECMAScript de Estágio 3 chamada "Static Class Features" por Daniel Ehrenberg e Jeff Morrison que visa resolver este problema. Junto com a proposta de "Campos de classe" do Estágio 3 , o código futuro será semelhante a este:

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'
    }
}

O acima é 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;

O Babel oferece suporte à transpilação de campos de classe por meio de @ babel / plugin-proposal-class-properties (incluído na predefinição do estágio 3 ), para que você possa usar esse recurso mesmo se o tempo de execução do JavaScript não o suportar.


Em comparação com a solução de @kangax de declarar um getter, essa solução também pode ter mais desempenho, já que aqui a propriedade é acessada diretamente em vez de chamar uma função.

Se essa proposta for aceita, será possível escrever o código JavaScript de uma forma mais semelhante às linguagens orientadas a objetos tradicionais, como Java e C♯.


Editar : Uma proposta de campos de classe unificados está agora no estágio 3; atualização para pacotes Babel v7.x.

Editar (fevereiro de 2020) : Os recursos de classe estática foram divididos em uma proposta diferente. Obrigado @ GOTO0!

Timothy Gu
fonte
Acho que a proposta relevante é na verdade esta ( recursos da classe estática ).
GOTO 0
29

Nos rascunhos atuais do ECMAScript 6 (em fevereiro de 2015), todas as propriedades da classe devem ser métodos, não valores (observe no ECMAScript uma "propriedade" é semelhante em conceito a um campo OOP, exceto que o valor do campo deve ser um Functionobjeto, não qualquer outro valor, como a Numberou Object).

Você ainda pode especificá-los usando os especificadores de propriedade do construtor ECMAScript tradicionais:

 class Agent {
 }
 Agent.CIRCLE = 1;
 Agent.SQUARE = 2;
 ...
Dai
fonte
11
Observe que a classsintaxe ES6 é apenas um açúcar sintático para funções e protótipos tradicionais do construtor JS.
Matt Browne
Acho que você gostaria de colocar essas propriedades no protótipo e não no construtor para que fiquem visíveis por meio de referências de propriedade de instâncias.
Pointy de
@Pointy eu inferi que o OP está tentando armazenar constantes para referência (quase como um C # / .net enum).
Dai
2
@MattBrowne Sim, mas para ser claro, a classsintaxe também tem algumas diferenças de nuances. Por exemplo, um método declarado com Class.prototype.method = function () {};é enumerável (visível com loops for-in), enquanto os classmétodos não são enumeráveis.
Timothy Gu
4

Para obter todas as vantagens da variável estática, segui essa abordagem. Para ser mais específico, podemos usá-lo para usar a variável privada ou ter apenas getter público, ou ter ambos getter ou setter. No último caso, é igual a uma das soluções postadas acima.

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'];

Eu poderia criar outra classe estendendo Url e funcionou.

Usei o babel para converter meu código ES6 para ES5

SM Adnan
fonte
1
O que é "vantagem total"? Não class Url { static getQueries… }; Url.staticMember = [];teria sido muito mais simples?
Bergi
Essas ===comparações tanto rendimento false, aliás
Bergi
"Vantagem total" significa que, da maneira acima, você pode manter _staticMember como privado, se desejar.
SM Adnan
-1

A resposta de @kangax não imita todo o comportamento estático da linguagem OOP tradicional, porque você não pode acessar a propriedade estática por sua instância como const agent = new Agent; agent.CIRCLE; // Undefined

Se você deseja acessar a propriedade estática como a OOP, aqui está minha solução:

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

NewApp.MULTIPLE_VERSIONS_SUPPORTED = true;

Teste o código da seguinte maneira.

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

legend80s
fonte
1
Acessar um staticcampo por uma instância seria bastante incomum, não é? Em algumas linguagens, como Java, o IDE realmente emite um aviso / dica se você fizer algo assim.
Isac
@Isac Sim, você está certo. O acesso por instância é desencorajado e minha resposta também. Apenas outra perspectiva da solução. 😀
legend80s