Alternativas à variável de classe ES6


493

Atualmente no ES5, muitos de nós estão usando o seguinte padrão em estruturas para criar classes e variáveis ​​de classe, o que é confortável:

// ES 5
FrameWork.Class({

    variable: 'string',
    variable2: true,

    init: function(){

    },

    addItem: function(){

    }

});

No ES6, você pode criar classes nativamente, mas não há opção para ter variáveis ​​de classe:

// ES6
class MyClass {
    const MY_CONST = 'string'; // <-- this is not possible in ES6
    constructor(){
        this.MY_CONST;
    }
}

Infelizmente, o acima não funcionará, pois apenas as classes podem conter métodos.

Eu entendo que eu posso this.myVar = trueem constructor... mas eu não quero 'junk' meu construtor, especialmente quando tenho 20-30 + parâmetros para uma classe maior.

Eu estava pensando em várias maneiras de lidar com esse problema, mas ainda não encontrei nenhum bom. (Por exemplo: crie um ClassConfigmanipulador e passe um parameterobjeto que é declarado separadamente da classe. Em seguida, o manipulador seria anexado à classe. Eu estava pensando em WeakMapstambém integrar, de alguma forma.)

Que tipo de idéias você teria para lidar com essa situação?


1
seu principal problema é que você terá uma repetição this.member = memberno seu construtor com 20 a 30 parâmetros?
Θεόφιλος Μουρατίδης

1
Você não pode simplesmente usar public variable2 = trueem classe? Isso o definiria no protótipo.

14
@ Θεόφιλος Μουρατίδης: Sim, e também quero usar meu construtor para procedimentos de inicialização e não para declarações de variáveis.
wintercounter

@derylius: Este é o principal problema, não possui esse recurso. Mesmo o uso público / privado ainda não foi decidido no rascunho do ES6. Faça um teste: es6fiddle.net
wintercounter

De acordo com o mais recente, tem esta função: wiki.ecmascript.org/doku.php?id=harmony:classes

Respostas:


515

Atualização de 2018:

Agora existe uma proposta de estágio 3 - Estou ansioso para tornar essa resposta obsoleta em alguns meses.

Enquanto isso, qualquer pessoa que usa TypeScript ou babel pode usar a sintaxe:

varName = value

Dentro de um corpo de declaração / expressão de classe, ele definirá uma variável. Espero que em alguns meses / semanas eu consiga postar uma atualização.

Atualização: o Chrome 74 agora é enviado com esta sintaxe funcionando.


As notas no wiki do ES para a proposta no ES6 ( classes mínimas mínimas ) observam:

Não há (intencionalmente) nenhuma maneira declarativa direta de definir propriedades de classe de protótipo de dados (exceto métodos) ou propriedade de instância

Propriedades de classe e propriedades de dados de protótipo precisam ser criadas fora da declaração.

As propriedades especificadas em uma definição de classe recebem os mesmos atributos como se elas aparecessem em um literal de objeto.

Isso significa que o que você está pedindo foi considerado e explicitamente decidido contra.

mas por que?

Boa pergunta. As pessoas boas do TC39 desejam que as declarações de classe declarem e definam os recursos de uma classe. Não são seus membros. Uma declaração de classe ES6 define seu contrato para o usuário.

Lembre-se, uma definição de classe define métodos de protótipo - definir variáveis ​​no protótipo geralmente não é algo que você faz. Você pode, é claro, usar:

constructor(){
    this.foo = bar
}

No construtor como você sugeriu. Veja também o resumo do consenso .

ES7 e além

Uma nova proposta para o ES7 está sendo trabalhada, permitindo variáveis ​​de instância mais concisas por meio de declarações e expressões de classe - https://esdiscuss.org/topic/es7-property-initializers


4
@wintercounter O importante a ser tirado é que permitir a definição de propriedades os definiria no protótipo como nos métodos e não em cada instância. Classes maximamente mínimas ainda estão em sua herança prototípica central. O que você realmente deseja fazer no seu caso é compartilhar estrutura e designar membros para cada instância. Simplesmente não é isso que as classes no ES6 objetivam - compartilhando a funcionalidade. Então, sim, para compartilhar a estrutura, você teria que seguir a sintaxe antiga. Até ES7, pelo menos :)
Benjamin Gruenbaum

5
Você pode mencionar staticpropriedades
Bergi

8
Opa, peido do cérebro. Eu esqueci que staticfunciona apenas para métodos também.
Bergi

638
Talvez as pessoas boas do TC39 devam nomear esse conceito como algo que não seja "classe" se não quiserem que ele se comporte como o resto do mundo da programação espera de algo chamado "classe".
Alex #

7
Veja também a proposta "Classe Fields & estática Propriedades" (já implementado em Babel : github.com/jeffmo/es-class-fields-and-static-properties
Matt Browne

127

Apenas para adicionar à resposta de Benjamin - variáveis ​​de classe são possíveis, mas você não usaria prototypepara defini-las.

Para uma variável de classe verdadeira, você gostaria de fazer algo como o seguinte:

class MyClass {}
MyClass.foo = 'bar';

De dentro de um método de classe, essa variável pode ser acessada como this.constructor.foo(ou MyClass.foo).

Essas propriedades de classe geralmente não seriam acessíveis para a instância de classe. ou seja, MyClass.foo'bar'mas new MyClass().fooéundefined

Se você também quiser acessar sua variável de classe a partir de uma instância, precisará definir adicionalmente um getter:

class MyClass {
    get foo() {
        return this.constructor.foo;
    }
}

MyClass.foo = 'bar';

Eu só testei isso com o Traceur, mas acredito que funcionará da mesma maneira em uma implementação padrão.

JavaScript realmente não tem classes . Mesmo com o ES6, estamos analisando uma linguagem baseada em objeto ou protótipo em vez de uma linguagem baseada em classe. Em qualquer function X () {}, X.prototype.constructoraponta para X. Quando o newoperador é usado X, um novo objeto é criado herdando X.prototype. Quaisquer propriedades indefinidas nesse novo objeto (inclusive constructor) são pesquisadas a partir daí. Podemos pensar nisso como gerar propriedades de objetos e classes.


29

Babel suporta variáveis ​​de classe no ESNext, verifique este exemplo :

class Foo {
  bar = 2
  static iha = 'string'
}

const foo = new Foo();
console.log(foo.bar, foo.iha, Foo.bar, Foo.iha);
// 2, undefined, undefined, 'string'

1
Você pode fazer da bar uma variável de classe privada fingindo-a com "#" assim: #bar = 2;
Lonnie Best

26

No seu exemplo:

class MyClass {
    const MY_CONST = 'string';
    constructor(){
        this.MY_CONST;
    }
}

Devido ao MY_CONST ser primitivo, https://developer.mozilla.org/pt-BR/docs/Glossary/Primitive , podemos fazer:

class MyClass {
    static get MY_CONST() {
        return 'string';
    }
    get MY_CONST() {
        return this.constructor.MY_CONST;
    }
    constructor() {
        alert(this.MY_CONST === this.constructor.MY_CONST);
    }
}
alert(MyClass.MY_CONST);
new MyClass

// alert: string ; true

Mas se MY_CONSTé do tipo de referência, como a static get MY_CONST() {return ['string'];}saída de alerta é string, false . Nesse caso, o deleteoperador pode fazer o truque:

class MyClass {
    static get MY_CONST() {
        delete MyClass.MY_CONST;
        return MyClass.MY_CONST = 'string';
    }
    get MY_CONST() {
        return this.constructor.MY_CONST;
    }
    constructor() {
        alert(this.MY_CONST === this.constructor.MY_CONST);
    }
}
alert(MyClass.MY_CONST);
new MyClass

// alert: string ; true

E finalmente para a variável de classe não const:

class MyClass {
    static get MY_CONST() {
        delete MyClass.MY_CONST;
        return MyClass.MY_CONST = 'string';
    }
    static set U_YIN_YANG(value) {
      delete MyClass.MY_CONST;
      MyClass.MY_CONST = value;
    }
    get MY_CONST() {
        return this.constructor.MY_CONST;
    }
    set MY_CONST(value) {
        this.constructor.MY_CONST = value;
    }
    constructor() {
        alert(this.MY_CONST === this.constructor.MY_CONST);
    }
}
alert(MyClass.MY_CONST);
new MyClass
// alert: string, true
MyClass.MY_CONST = ['string, 42']
alert(MyClass.MY_CONST);
new MyClass
// alert: string, 42 ; true

5
Evite o deleteoperador, se estiver sozinho por razões de desempenho. O que você realmente quer aqui é Object.defineProperty.
Bergi 23/10/2015

@ shinzou eu estava pensando a mesma coisa. Não necessariamente a culpa do OP; é realmente a linguagem que carece de mecanismos adequados para representar dados de uma maneira que reflita seus relacionamentos no mundo real.
User1974458

17

Como o seu problema é principalmente estilístico (não querendo encher o construtor com várias declarações), ele também pode ser resolvido estilisticamente.

Do meu ponto de vista, muitas linguagens baseadas em classes têm o construtor como uma função com o nome do próprio nome da classe. Estilisticamente, poderíamos usar isso para criar uma classe ES6 que estilisticamente ainda faz sentido, mas não agrupa as ações típicas que ocorrem no construtor com todas as declarações de propriedade que estamos fazendo. Simplesmente usamos o construtor JS real como a "área de declaração" e, em seguida, criamos uma classe denominada função que de outra forma tratamos como a área "outras coisas do construtor", chamando-a no final do construtor verdadeiro.

"use strict";

classe MyClass
{
    // apenas declare suas propriedades e chame this.ClassName (); daqui
    construtor(){
        this.prop1 = 'blá 1';
        this.prop2 = 'blá 2';
        this.prop3 = 'blá 3';
        this.MyClass ();
    }

    // todos os tipos de outras coisas "construtoras", não mais confundidas com declarações
    Minha classe() {
        doWhatever ();
    }
}

Ambos serão chamados quando a nova instância for construída.

É como ter dois construtores nos quais você separa as declarações e as outras ações que deseja executar, e estilisticamente torna difícil demais entender o que está acontecendo.

Acho que é um bom estilo de usar ao lidar com muitas declarações e / ou muitas ações que precisam acontecer na instanciação e que desejam manter as duas idéias distintas uma da outra.


OBSERVAÇÃO : propositalmente, não uso as idéias idiomáticas típicas de "inicializar" (como um init()ou initialize()método) porque essas são frequentemente usadas de maneira diferente. Existe uma espécie de diferença presumida entre a ideia de construir e inicializar. Ao trabalhar com construtores, as pessoas sabem que são chamadas automaticamente como parte da instanciação. Vendo um initmétodo que muitas pessoas presumem, sem uma segunda olhada, que precisam fazer algo ao longo da forma var mc = MyClass(); mc.init();, porque é assim que você normalmente inicializa. Não estou tentando adicionar um processo de inicialização para o usuário da classe, estou tentando adicionar ao processo de construção da própria classe.

Embora algumas pessoas possam dar uma olhada duas vezes por um momento, esse é realmente o ponto: comunica a elas que a intenção faz parte da construção, mesmo que isso as faça dar uma olhada duas vezes e ir "não é como os construtores do ES6 funcionam "e dê uma segunda olhada no construtor real para dizer" ah, eles chamam isso de baixo, eu vejo ", isso é muito melhor do que NÃO comunicar essa intenção (ou comunicar incorretamente) e provavelmente obter muito pessoas usando errado, tentando inicializá-lo de fora e lixo. Isso é muito intencional para o padrão que sugiro.


Para aqueles que não querem seguir esse padrão, o oposto exato também pode funcionar. Farm as declarações para outra função no início. Talvez o nomeie "propriedades" ou "publicProperties" ou algo assim. Em seguida, coloque o restante do material no construtor normal.

"use strict";

classe MyClass
{
    properties () {
        this.prop1 = 'blá 1';
        this.prop2 = 'blá 2';
        this.prop3 = 'blá 3';
    }

    constructor () {
        this.properties ();
        doWhatever ();
    }
}

Observe que esse segundo método pode parecer mais limpo, mas também tem um problema inerente, onde propertiesé substituído quando uma classe usando esse método se estende a outra. Você precisaria dar nomes mais exclusivos propertiespara evitar isso. Meu primeiro método não tem esse problema porque sua metade falsa do construtor é nomeada exclusivamente após a classe.


1
Por favor, não use um método de protótipo com o nome da própria classe. Isso não é idiomático no JS, não tente fazer com que pareça outro idioma. Se você realmente quiser usar essa abordagem, o nome canônico para o método é init.
Bergi

1
@ Bergi - inité um padrão usado com frequência e costuma ser chamado de fora da classe quando o usuário externo deseja inicializar, ou seja var b = new Thing(); b.init();. Essa é uma opção estilística 100% que eu preferiria comunicar que é uma segunda função que é automaticamente chamada, aproveitando os padrões encontrados em outros idiomas. É muito menos provável que alguém olhe para isso e suponha que precisa chamar o MyClassmétodo de fora, mais provavelmente perceberá que a intenção é um segundo método atuando na construção (isto é, chamado por si só na instanciação).
Jimbo Jonny

Hum, eu posso perceber isso olhando para o construtor, mas não pelo nome do método MyClass.
Bergi

1
@ Bergi - Pegar um padrão de outra linguagem e aplicá-lo ao JS de uma maneira que não é tecnicamente o que está acontecendo, mas ainda funciona, não é completamente sem precedentes. Você não pode me dizer que ninguém notou que a $myvarmaneira padrão de se referir a variáveis ​​destinadas a conter objetos jQuery não é convenientemente semelhante ao padrão de ter $ no início de variáveis ​​às quais muitos programadores PHP estão acostumados. Apenas essa pequena implicação de que "sim, não é exatamente a mesma coisa ... mas veja ... ainda é uma variável, porque é assim que as variáveis ​​são feitas em alguns idiomas!" ajuda.
Jimbo Jonny

1
Eu prefiro sua última opção com a properties()função No entanto, as coisas ficam um pouco mais complicadas ao estender as classes, portanto, sugiro que se você usar uma properties()função em todas as suas classes para declarar propriedades de classe, prefixe o nome do método com o nome da classe (IE: MyClassProperties()para evitar substituição acidental) . chamadas de função dentro de subclasses Além disso, mantenha em mente que todas as chamadas para super()deve ser declarado primeiro no construtor da classe.
TheDarkIn1978

14

E o jeito da velha escola?

class MyClass {
     constructor(count){ 
          this.countVar = 1 + count;
     }
}
MyClass.prototype.foo = "foo";
MyClass.prototype.countVar = 0;

// ... 

var o1 = new MyClass(2); o2 = new MyClass(3);
o1.foo = "newFoo";

console.log( o1.foo,o2.foo);
console.log( o1.countVar,o2.countVar);

No construtor, você menciona apenas os vars que precisam ser computados. Gosto da herança de protótipo para esse recurso - ele pode ajudar a economizar muita memória (caso haja muitos vars nunca atribuídos).


4
Isso não economiza memória e apenas piora o desempenho do que se você os tivesse declarado no construtor. codereview.stackexchange.com/a/28360/9258
Esailija

2
Além disso, isso anula o objetivo de usar as classes ES6 em primeiro lugar. Tal como está, parece quase impossível de usar ES6 classes como classes de reais oop, que é um pouco decepcionante ...
Kokodoko

4
@ Kokodoko real oop - você quer dizer, como em Java? Concordo, notei que muitas pessoas se irritam com o JS porque tentam usá-lo como Java, como costumavam se acostumar, porque a sintaxe do JS é semelhante e assumem que funciona da mesma maneira ... por dentro, é bem diferente, como sabemos.
Zarkone 23/12/15

2
Não se trata apenas da sintaxe da linguagem. A filosofia OOP se presta muito bem à programação em geral - especialmente ao criar aplicativos e jogos. O JS é tradicionalmente implementado na criação de páginas da Web, que é bem diferente. De alguma forma, essas duas abordagens precisam se unir, já que a Web também está se tornando mais sobre aplicativos.
Kokodoko

1
@Kokodoko Só porque é uma OOP diferente , não significa que não é OOP. Os protótipos são uma abordagem OOP 100% válida; ninguém vai chamar de "não-OOP" porque usa protótipos. Não combinar outro paradigma de POO significa exatamente isso: é diferente.
Dave Newton

13

Como Benjamin disse em sua resposta, o TC39 decidiu explicitamente não incluir esse recurso pelo menos no ES2015. No entanto, o consenso parece ser que eles serão adicionados no ES2016.

A sintaxe ainda não foi decidida, mas há uma proposta preliminar para o ES2016 que permitirá declarar propriedades estáticas em uma classe.

Graças à magia de babel, você pode usar isso hoje. Habilite a transformação das propriedades da classe de acordo com estas instruções e você estará pronto. Aqui está um exemplo da sintaxe:

class foo {
  static myProp = 'bar'
  someFunction() {
    console.log(this.myProp)
  }
}

Esta proposta está em um estágio muito inicial, portanto, esteja preparado para ajustar sua sintaxe com o passar do tempo.


Sim, isso não é ES6. A menos que você saiba o que é, você não deve usá-lo.
Bergi

10

[Tópico longo, não tenho certeza se ele já está listado como uma opção ...].
Uma alternativa simples apenas para os participantes seria definir a const fora da classe. Isso estará acessível apenas no próprio módulo, a menos que seja acompanhado por um getter.
Desta forma, prototypenão está desarrumado e você recebe o const.

// will be accessible only from the module itself
const MY_CONST = 'string'; 
class MyClass {

    // optional, if external access is desired
    static get MY_CONST(){return MY_CONST;}

    // access example
    static someMethod(){
        console.log(MY_CONST);
    }
}

O que acontece se você a) usa um em varvez de const2) usa várias instâncias dessa classe? Em seguida as variáveis externas será alterado com cada instância da classe
nick Carraway

1
fail point @nickcarraway, minha oferta significa apenas const, não como a pergunta intitulada.
Hertzel Guinness

6

ES7 sintaxe do membro da classe:

ES7tem uma solução para 'juntar' sua função de construtor. Aqui está um exemplo:

class Car {
  
  wheels = 4;
  weight = 100;

}

const car = new Car();
console.log(car.wheels, car.weight);

O exemplo acima ficaria com o seguinte em ES6:

class Car {

  constructor() {
    this.wheels = 4;
    this.weight = 100;
  }

}

const car = new Car();
console.log(car.wheels, car.weight);

Ao usar isso, esteja ciente de que esta sintaxe pode não ser suportada por todos os navegadores e pode ter que ser transpilada para uma versão anterior do JS.

Bônus: uma fábrica de objetos:

function generateCar(wheels, weight) {

  class Car {

    constructor() {}

    wheels = wheels;
    weight = weight;

  }

  return new Car();

}


const car1 = generateCar(4, 50);
const car2 = generateCar(6, 100);

console.log(car1.wheels, car1.weight);
console.log(car2.wheels, car2.weight);


3
Você realizou variáveis ​​de instância, não variáveis ​​de classe.
Tjad Clark

5

Você pode imitar o comportamento das classes es6 ... e usar suas variáveis ​​de classe :)

Olha mãe ... sem aulas!

// Helper
const $constructor = Symbol();
const $extends = (parent, child) =>
  Object.assign(Object.create(parent), child);
const $new = (object, ...args) => {
  let instance = Object.create(object);
  instance[$constructor].call(instance, ...args);
  return instance;
}
const $super = (parent, context, ...args) => {
  parent[$constructor].call(context, ...args)
}
// class
var Foo = {
  classVariable: true,

  // constructor
  [$constructor](who){
    this.me = who;
    this.species = 'fufel';
  },

  // methods
  identify(){
    return 'I am ' + this.me;
  }
}

// class extends Foo
var Bar = $extends(Foo, {

  // constructor
  [$constructor](who){
    $super(Foo, this, who);
    this.subtype = 'barashek';
  },

  // methods
  speak(){
    console.log('Hello, ' + this.identify());
  },
  bark(num){
    console.log('Woof');
  }
});

var a1 = $new(Foo, 'a1');
var b1 = $new(Bar, 'b1');
console.log(a1, b1);
console.log('b1.classVariable', b1.classVariable);

Coloquei no GitHub


0

A maneira como resolvi isso, que é outra opção (se você tiver o jQuery disponível), foi definir os campos em um objeto da velha escola e depois estender a classe com esse objeto. Eu também não queria encher o construtor de tarefas, essa parecia ser uma solução interessante.

function MyClassFields(){
    this.createdAt = new Date();
}

MyClassFields.prototype = {
    id : '',
    type : '',
    title : '',
    createdAt : null,
};

class MyClass {
    constructor() {
        $.extend(this,new MyClassFields());
    }
};

- Atualização Após o comentário de Bergi.

Nenhuma versão JQuery:

class SavedSearch  {
    constructor() {
        Object.assign(this,{
            id : '',
            type : '',
            title : '',
            createdAt: new Date(),
        });

    }
}

Você ainda acaba com o construtor 'fat', mas pelo menos é tudo em uma classe e atribuído em um hit.

EDIÇÃO # 2: Eu já fiz um círculo completo e agora estou atribuindo valores no construtor, por exemplo

class SavedSearch  {
    constructor() {
        this.id = '';
        this.type = '';
        this.title = '';
        this.createdAt = new Date();
    }
}

Por quê? Simples, usando os comentários acima, além de alguns comentários do JSdoc, o PHPStorm conseguiu executar o preenchimento de código nas propriedades. Atribuir todos os vars em uma ocorrência foi bom, mas a incapacidade de codificar concluir as propriedades, imo, não vale o benefício de desempenho (quase certamente minúsculo).


2
Se você pode fazer classsintaxe, também é possível Object.assign. Não há necessidade de jQuery.
Bergi 10/05/19

Não vejo sentido em criar MyClassFieldsum construtor - ele não possui métodos. Você pode explicar por que você fez isso (em vez de, digamos, uma função simples de fábrica que retorna um objeto literal)?
Bergi 10/10/19

@ Bergi - Provavelmente força de hábito e não qualquer outra coisa, para ser honesto. Também foi uma boa chamada sobre Object.assign - não sabia disso!
9788 Steve Childs #

0

Bem, você pode declarar variáveis ​​dentro do Construtor.

class Foo {
    constructor() {
        var name = "foo"
        this.method = function() {
            return name
        }
    }
}

var foo = new Foo()

foo.method()

1
Você precisará criar uma instância dessa classe para usar essa variável. Funções estáticas não podem acessar essa variável
Aditya

0

Ainda assim, você não pode declarar nenhuma classe como em outras linguagens de programação. Mas você pode criar tantas variáveis ​​de classe. Mas o problema é o escopo do objeto de classe. Então, de acordo com mim, a melhor maneira de programação OOP no ES6 Javascript: -

class foo{
   constructor(){
     //decalre your all variables
     this.MY_CONST = 3.14;
     this.x = 5;
     this.y = 7;
     // or call another method to declare more variables outside from constructor.
     // now create method level object reference and public level property
     this.MySelf = this;
     // you can also use var modifier rather than property but that is not working good
     let self = this.MySelf;
     //code ......... 
   }
   set MySelf(v){
      this.mySelf = v;
   }
   get MySelf(v){
      return this.mySelf;
   }
   myMethod(cd){
      // now use as object reference it in any method of class
      let self = this.MySelf;
      // now use self as object reference in code
   }
}

-1

Esta é uma combinação um pouco hackiana de estática e funciona para mim

class ConstantThingy{
        static get NO_REENTER__INIT() {
            if(ConstantThingy._NO_REENTER__INIT== null){
                ConstantThingy._NO_REENTER__INIT = new ConstantThingy(false,true);
            }
            return ConstantThingy._NO_REENTER__INIT;
        }
}

usado em outro lugar

var conf = ConstantThingy.NO_REENTER__INIT;
if(conf.init)...

7
Eu recomendaria singleton_instancecomo um nome de propriedade para que todos entendam o que você está fazendo aqui.
Bergi 12/09/2015
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.