Alternativas variables de clase ES6

493

Actualmente en ES5, muchos de nosotros estamos usando el siguiente patrón en marcos para crear clases y variables de clase, lo cual es cómodo:

// ES 5
FrameWork.Class({

    variable: 'string',
    variable2: true,

    init: function(){

    },

    addItem: function(){

    }

});

En ES6 puede crear clases de forma nativa, pero no hay opción para tener variables de clase:

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

Lamentablemente, lo anterior no funcionará, ya que las clases solo pueden contener métodos.

Entiendo que puedo this.myVar = trueen constructor... pero yo no quiero 'basura' mi constructor, especialmente cuando tengo 20-30 + parametros para una clase más grande.

Estaba pensando en muchas formas de manejar este problema, pero aún no he encontrado ninguna buena. (Por ejemplo: cree un ClassConfigcontrolador y pase un parameterobjeto, que se declara por separado de la clase. Luego, el controlador se adjuntaría a la clase. Estaba pensando WeakMapsen integrarme de alguna manera).

¿Qué tipo de ideas tendrías para manejar esta situación?

contador de invierno
fuente
1
Su principal problema es que tendrá una repetición this.member = memberen su constructor con 20-30 parámetros?
Θεόφιλος Μουρατίδης
1
¿No puedes usar public variable2 = trueen clase? Esto lo definiría en el prototipo.
14
@ Θεόφιλος Μουρατίδης: Sí, y también quiero usar mi constructor para los procedimientos de inicialización y no para las declaraciones de variables.
wintercounter
@derylius: este es el problema principal, no tiene esa característica. Incluso el uso público / privado aún no se decide en el borrador de ES6. Déle una vuelta de prueba: es6fiddle.net
wintercounter
Según lo último, tiene esta función: wiki.ecmascript.org/doku.php?id=harmony:classes

Respuestas:

515

Actualización 2018:

Ahora hay una propuesta de la etapa 3: espero que esta respuesta quede obsoleta en unos meses.

Mientras tanto, cualquiera que use TypeScript o babel puede usar la sintaxis:

varName = value

Dentro de un cuerpo de declaración / expresión de clase y definirá una variable. Espero que en unos meses / semanas pueda publicar una actualización.

Actualización: Chrome 74 ahora viene con esta sintaxis funcionando.


Las notas en el wiki de ES para la propuesta en ES6 ( clases mínimamente máximas ) nota:

No existe (intencionalmente) una forma declarativa directa para definir propiedades de datos de prototipo (que no sean métodos) propiedades de clase o propiedad de instancia

Las propiedades de clase y las propiedades de datos prototipo deben crearse fuera de la declaración.

A las propiedades especificadas en una definición de clase se les asignan los mismos atributos que si aparecieran en un objeto literal.

Esto significa que lo que está pidiendo se consideró y se decidió explícitamente en contra.

¿pero por qué?

Buena pregunta. La buena gente de TC39 quiere que las declaraciones de clase declaren y definan las capacidades de una clase. No sus miembros. Una declaración de clase ES6 define su contrato para su usuario.

Recuerde, una definición de clase define los métodos de prototipo : definir variables en el prototipo generalmente no es algo que haga. Por supuesto, puede usar:

constructor(){
    this.foo = bar
}

En el constructor como sugeriste. Ver también el resumen del consenso .

ES7 y más allá

Se está trabajando en una nueva propuesta para ES7 que permite variables de instancia más concisas a través de declaraciones y expresiones de clase: https://esdiscuss.org/topic/es7-property-initializers

Benjamin Gruenbaum
fuente
44
@wintercounter lo importante es que permitir definir propiedades las definiría en el prototipo como los métodos y no en cada instancia. Las clases mínimamente máximas todavía están en su núcleo de herencia prototípica. Lo que realmente quiere hacer en su caso es compartir la estructura y asignar miembros para cada instancia. Esto no es para lo que apuntan las clases en ES6: compartir la funcionalidad. Entonces sí, para compartir la estructura, tendría que atenerse a la sintaxis anterior. Hasta ES7 al menos :)
Benjamin Gruenbaum
55
Es posible que desee mencionar staticpropiedades
Bergi
8
Vaya, pedo cerebral. Olvidé que también staticfunciona solo para métodos.
Bergi
638
Tal vez las buenas personas en TC39 deberían llamar a este concepto algo diferente a "clase" si no quieren que se comporte como el resto del mundo de la programación espera de algo llamado "clase".
Alex
77
Consulte también la propuesta "Campos de clase y propiedades estáticas" (ya implementada en Babel : github.com/jeffmo/es-class-fields-and-static-properties
Matt Browne
127

Solo para agregar a la respuesta de Benjamin: las variables de clase son posibles, pero no las usaría prototypepara configurarlas.

Para una verdadera variable de clase, querría hacer algo como lo siguiente:

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

Desde un método de clase se puede acceder a esa variable como this.constructor.foo(o MyClass.foo).

Estas propiedades de clase generalmente no serían accesibles desde la instancia de clase. es decir, MyClass.fooda 'bar'pero new MyClass().fooesundefined

Si también desea tener acceso a su variable de clase desde una instancia, deberá definir adicionalmente un getter:

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

MyClass.foo = 'bar';

Solo he probado esto con Traceur, pero creo que funcionará igual en una implementación estándar.

JavaScript realmente no tiene clases . Incluso con ES6 estamos viendo un lenguaje basado en objetos o prototipos en lugar de un lenguaje basado en clases. En cualquiera function X () {}, X.prototype.constructorapunta de nuevo a X. Cuando newse utiliza el operador X, se crea un nuevo objeto heredando X.prototype. Todas las propiedades indefinidas en ese nuevo objeto (incluido constructor) se buscan desde allí. Podemos pensar en esto como generar propiedades de objetos y clases.

lyschoening
fuente
29

Babel admite variables de clase en ESNext, consulte este ejemplo :

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'
Kosmetika
fuente
1
Puede hacer que la barra sea una variable de clase privada simulando con "#" de esta manera: #bar = 2;
Lonnie Best
26

En tu ejemplo:

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

Debido a que MY_CONST es primitivo https://developer.mozilla.org/en-US/docs/Glossary/Primitive solo podemos hacer:

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

Pero si el MY_CONSTtipo de referencia como static get MY_CONST() {return ['string'];}salida de alerta es cadena, falso . En tal caso, el deleteoperador puede hacer el truco:

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

Y finalmente para la variable de clase no 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
Oleg Mazko
fuente
55
Evite al deleteoperador, si está solo, por razones de rendimiento. Lo que realmente quieres aquí es Object.defineProperty.
Bergi
@shinzou Estaba pensando lo mismo. Sin embargo, no necesariamente es culpa del OP; Es realmente el lenguaje que carece de los mecanismos adecuados para representar los datos de una manera que refleje sus relaciones en el mundo real.
user1974458
17

Dado que su problema es principalmente estilístico (no quiere llenar el constructor con un montón de declaraciones), también se puede resolver estilísticamente.

A mi modo de ver, muchos lenguajes basados ​​en clases tienen el constructor como una función nombrada después del nombre de la clase. Estilísticamente podríamos usar eso para hacer una clase ES6 que estilísticamente todavía tiene sentido pero no agrupa las acciones típicas que tienen lugar en el constructor con todas las declaraciones de propiedades que estamos haciendo. Simplemente usamos el constructor JS real como el "área de declaración", luego hacemos una función llamada clase que de otro modo tratamos como el área "otras cosas del constructor", llamándola al final del verdadero constructor.

"uso estricto";

clase MyClass
{
    // solo declara tus propiedades y luego llama a esto. ClassName (); de aquí
    constructor(){
        this.prop1 = 'bla 1';
        this.prop2 = 'blah 2';
        this.prop3 = 'blah 3';
        this.MyClass ();
    }

    // todo tipo de otras cosas de "constructor", que ya no están mezcladas con declaraciones
    Mi clase() {
        Haz lo que sea();
    }
}

Ambos serán llamados a medida que se construya la nueva instancia.

Es como tener 2 constructores donde separa las declaraciones y las otras acciones de constructor que desea realizar, y estilísticamente hace que no sea demasiado difícil entender que eso también está sucediendo.

Creo que es un buen estilo para usar cuando se trata de muchas declaraciones y / o muchas acciones que deben ocurrir en la instanciación y que desean mantener las dos ideas distintas entre sí.


NOTA : no uso las ideas idiomáticas típicas de "inicialización" (como un método init()o initialize()) porque a menudo se usan de manera diferente. Hay una especie de presunta diferencia entre la idea de construir e inicializar. Al trabajar con constructores, las personas saben que se les llama automáticamente como parte de la creación de instancias. Al ver un initmétodo, muchas personas van a suponer, sin una segunda mirada, que necesitan hacer algo a lo largo de la forma var mc = MyClass(); mc.init();, porque así es como normalmente se inicializa. No estoy tratando de agregar un proceso de inicialización para el usuario de la clase, estoy tratando de agregar al proceso de construcción de la clase en sí.

Si bien algunas personas pueden hacer una doble toma por un momento, ese es realmente el punto: les comunica que la intención es parte de la construcción, incluso si eso les hace hacer una pequeña toma doble y decir "eso no es cómo funcionan los constructores de ES6 "y toman un segundo vistazo al constructor real para decir" oh, lo llaman en la parte inferior, ya veo ", eso es mucho mejor que NO comunicar esa intención (o comunicarla incorrectamente) y probablemente obtener mucha información personas que lo usan mal, tratando de inicializarlo desde afuera y basura. Eso es muy intencional al patrón que sugiero.


Para aquellos que no quieren seguir ese patrón, todo lo contrario también puede funcionar. Agrupe las declaraciones a otra función al principio. Quizás lo denomine "propiedades" o "publicProperties" o algo así. Luego pon el resto de las cosas en el constructor normal.

"uso estricto";

clase MyClass
{
    propiedades () {
        this.prop1 = 'bla 1';
        this.prop2 = 'blah 2';
        this.prop3 = 'blah 3';
    }

    constructor () {
        this.properties ();
        Haz lo que sea();
    }
}

Tenga en cuenta que este segundo método puede parecer más limpio, pero también tiene un problema inherente en el que propertiesse anula cuando una clase que usa este método extiende a otra. Tendría que dar más nombres únicos propertiespara evitar eso. Mi primer método no tiene este problema porque su mitad falsa del constructor lleva el nombre exclusivo de la clase.

Jimbo Jonny
fuente
1
No utilice un método prototipo con el nombre de la clase misma. Eso no es idiomático en JS, no intentes hacer que se vea como otro idioma. Si realmente desea utilizar este enfoque, el nombre canónico para el método es init.
Bergi
1
@Bergi: inites un patrón que se usa con frecuencia y que a menudo se llama desde fuera de la clase cuando el usuario externo quiere inicializar, es decir var b = new Thing(); b.init();. Esta es una opción 100% estilística que preferiría comunicar que es una segunda función que se llama automáticamente al aprovechar los patrones que se encuentran en otros idiomas. Es mucho menos probable que alguien vea esto y asuma que necesita llamar al MyClassmétodo desde afuera, más probable que se dé cuenta de que la intención es un segundo método que actúa en la construcción (es decir, llamado por sí mismo en la instanciación).
Jimbo Jonny
Hm, podría darme cuenta mirando el constructor, pero no por el nombre del método MyClass.
Bergi
1
@Bergi: tomar un patrón de otro idioma y aplicarlo a JS de una manera que técnicamente no es lo que está sucediendo, pero que aún funciona, no está completamente sin precedentes. No puede decirme que nadie se ha dado cuenta de que la $myvarforma estándar de referirse a las variables destinadas a contener objetos jQuery no es convenientemente similar al patrón de tener $ al comienzo de las variables a las que están acostumbrados tantos programadores PHP. Solo esa pequeña implicación de que "sí, no es exactamente lo mismo ... pero mira ... ¡sigue siendo una variable porque esa es la forma en que las variables se hacen en algunos idiomas!" ayuda
Jimbo Jonny
1
Prefiero tu última opción con la properties()función. Sin embargo, las cosas se vuelven un poco más complicadas al extender las clases, por lo que sugeriría que si usa una properties()función en todas sus clases para declarar sus propiedades de clase, prefija el nombre del método con el nombre de la clase (IE: MyClassProperties()para evitar anular accidentalmente llamadas a funciones dentro de subclases. Además, tenga en cuenta que cualquier llamada a super()debe declararse primero en el constructor de la clase.
TheDarkIn1978
14

¿Qué hay de la vieja escuela?

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);

En el constructor solo mencionas los vars que deben calcularse. Me gusta la herencia de prototipos para esta característica: puede ayudar a ahorrar mucha memoria (en caso de que haya muchos vars nunca asignados).

zarkone
fuente
44
Esto no ahorra memoria y solo hace que el rendimiento sea mucho peor que si los hubiera declarado en el constructor. codereview.stackexchange.com/a/28360/9258
Esailija
2
Además, esto anula el propósito de usar las clases ES6 en primer lugar. Tal como está, parece que casi no es posible usar las clases de ES6 como las clases reales oop, lo cual es algo decepcionante ...
Kokodoko
44
@Kokodoko real oop - quieres decir, como en Java? Estoy de acuerdo, he notado que muchas personas están enojadas con JS porque intentan usarlo como Java, como se acostumbran, porque la sintaxis de JS se ve similar, y asumen que funciona de la misma manera ... Pero a partir de por dentro, es bastante diferente, como sabemos.
zarkone
2
Sin embargo, no se trata solo de la sintaxis del lenguaje. La filosofía OOP se presta bastante bien a la programación en general, especialmente al crear aplicaciones y juegos. JS se ha implementado tradicionalmente en la creación de páginas web, que es bastante diferente. De alguna manera, esos dos enfoques deben unirse, ya que la web también se está volviendo más sobre las aplicaciones.
Kokodoko
1
@Kokodoko El hecho de que sea una OOP diferente no significa que no sea una OOP. Los prototipos son un enfoque OOP 100% válido; nadie va a llamar a Self "no OOP" porque usa prototipos. No coincidir con otro paradigma de OOP significa solo eso: es diferente.
Dave Newton
13

Como Benjamin dijo en su respuesta, TC39 decidió explícitamente no incluir esta característica al menos para ES2015. Sin embargo, el consenso parece ser que lo agregarán en ES2016.

La sintaxis aún no se ha decidido, pero hay una propuesta preliminar para ES2016 que le permitirá declarar propiedades estáticas en una clase.

Gracias a la magia de babel, puedes usar esto hoy. Habilite la transformación de las propiedades de clase de acuerdo con estas instrucciones y listo. Aquí hay un ejemplo de la sintaxis:

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

Esta propuesta está en un estado muy temprano, así que prepárate para ajustar tu sintaxis a medida que pasa el tiempo.

BonsaiOak
fuente
Sí, eso no es ES6. A menos que sepa lo que es, no debe usarlo.
Bergi
10

[Hilo largo, no estoy seguro si ya está en la lista como una opción ...].
Una alternativa simple solo para los concursantes sería definir la constante fuera de la clase. Solo será accesible desde el módulo en sí, a menos que esté acompañado de un captador.
Esta manera prototypeno está llena de basura y obtienes el 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);
    }
}
Hertzel Guinness
fuente
¿Qué sucede si a) usa un en varlugar de const2) usa varias instancias de esta clase? Luego, las variables externas se cambiarán con cada instancia de la clase
nick carraway
1
punto de falla @nickcarraway, mi oferta es solo para constante, no como la pregunta titulada.
Hertzel Guinness
6

ES7 sintaxis de miembro de clase:

ES7tiene una solución para 'desechar' su función de constructor. Aquí hay un ejemplo:

class Car {
  
  wheels = 4;
  weight = 100;

}

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

El ejemplo anterior se vería de la siguiente manera ES6:

class Car {

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

}

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

Tenga en cuenta al usar esto que es posible que esta sintaxis no sea compatible con todos los navegadores y que deba transpilarse una versión anterior de JS.

Bonus: una 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);

Willem van der Veen
fuente
3
Has logrado variables de instancia, no variables de clase.
Tjad Clark el
5

Puede imitar el comportamiento de las clases es6 ... y usar sus variables de clase :)

Mira mamá ... no hay clases!

// 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);

Lo puse en GitHub

Ruslan
fuente
0

La forma en que resolví esto, que es otra opción (si tiene jQuery disponible), fue definir los campos en un objeto de la vieja escuela y luego extender la clase con ese objeto. Tampoco quería salpicar al constructor con tareas, esto parecía ser una buena solución.

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

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

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

- Actualización Siguiendo el comentario de Bergi.

Sin versión JQuery:

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

    }
}

Todavía terminas con un constructor 'gordo', pero al menos todo en una clase y asignado en un solo golpe.

EDITAR # 2: ahora he completado el círculo y ahora estoy asignando valores en el constructor, por ejemplo

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

¿Por qué? Realmente simple, usando lo anterior más algunos comentarios de JSdoc, PHPStorm pudo completar el código en las propiedades. Asignar todos los vars en un solo golpe fue bueno, pero la imposibilidad de codificar para completar las propiedades, en mi opinión, no vale la pena (casi ciertamente minúscula) beneficio de rendimiento.

Steve Childs
fuente
2
Si puede hacer la classsintaxis, también puede hacerlo Object.assign. No hay necesidad de jQuery.
Bergi
No veo ningún punto para hacer MyClassFieldsun constructor, no tiene ningún método. ¿Puedes explicar por qué hiciste esto (en lugar de, por ejemplo, una función de fábrica simple que devuelve un objeto literal)?
Bergi
@Bergi - Probablemente fuerza de hábito en lugar de cualquier otra cosa, para ser honesto. También buena llamada sobre Object.assign, ¡no sabía eso!
Steve Childs
0

Bueno, puedes declarar variables dentro del Constructor.

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

var foo = new Foo()

foo.method()
Osama Xäwãñz
fuente
1
Deberá crear una instancia de esta clase para usar esta variable. Las funciones estáticas no pueden acceder a esa variable
Aditya
0

Aún así, no puede declarar ninguna clase como en otros lenguajes de programación. Pero puedes crear tantas variables de clase. Pero el problema es el alcance del objeto de clase. Entonces, según yo, la mejor forma de programación OOP en 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
   }
}
Tyler
fuente
-1

Este es un combo un poco hack de estática y funciona para mí

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 en otro lugar

var conf = ConstantThingy.NO_REENTER__INIT;
if(conf.init)...
TroyWorks
fuente
77
Lo recomendaría singleton_instancecomo nombre de propiedad para que todos entiendan lo que está haciendo aquí.
Bergi