web-dev-qa-db-fra.com

Variantes de classe ES6 alternatives

Actuellement dans ES5, beaucoup d’entre nous utilisent le modèle suivant dans les frameworks pour créer des classes et des variables de classe, ce qui est confortable:

// ES 5
FrameWork.Class({

    variable: 'string',
    variable2: true,

    init: function(){

    },

    addItem: function(){

    }

});

Dans ES6, vous pouvez créer des classes de manière native, mais il n’existe aucune option permettant d’avoir des variables de classe:

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

Malheureusement, ce qui précède ne fonctionnera pas, car les classes ne peuvent contenir que des méthodes.

Je comprends que je peux this.myVar = true dans constructor… mais je ne veux pas "salir" mon constructeur, surtout quand j'ai 20-30 + paramètres pour une classe plus grande.

Je pensais à plusieurs façons de gérer ce problème, mais je n’en ai pas encore trouvé de bon. (Par exemple: créez un gestionnaire ClassConfig et transmettez un objet parameter, qui est déclaré séparément de la classe. Ensuite, le gestionnaire s’attacherait à la classe. Je pensais aussi à WeakMaps à intégrer, en quelque sorte.)

Quel genre d'idées auriez-vous pour gérer cette situation?

468
wintercounter

Mise à jour 2018:

Il y a maintenant une proposition pour l'étape 3 - je suis impatient de rendre cette réponse obsolète dans quelques mois.

Entre-temps, toute personne utilisant TypeScript ou babel peut utiliser la syntaxe suivante:

varName = value

Dans un corps de déclaration/expression de classe, il définira une variable. J'espère que dans quelques mois/semaines, je pourrai poster une mise à jour.

Mise à jour: Chrome 74 est à présent livré avec cette syntaxe.


Les notes dans le wiki ES pour la proposition dans ES6 ( classes minimales minimales ) note:

Il n'y a (intentionnellement) pas de moyen déclaratif direct pour définir des propriétés de données prototypes (autres que des méthodes), des propriétés de classe ou des propriétés d'instance.

Les propriétés de classe et les propriétés de données prototypes doivent être créées en dehors de la déclaration.

Les propriétés spécifiées dans une définition de classe se voient attribuer les mêmes attributs que si elles apparaissaient dans un littéral d'objet.

Cela signifie que ce que vous demandez a été pris en compte et explicitement déconseillé.

mais pourquoi?

Bonne question. Les personnes compétentes de TC39 veulent que les déclarations de classe déclarent et définissent les capacités d’une classe. Pas ses membres. Une déclaration de classe ES6 définit son contrat pour son utilisateur.

N'oubliez pas qu'une définition de classe définit des méthodes de prototype - la définition de variables sur le prototype ne fait généralement pas partie de vos tâches. Vous pouvez bien sûr utiliser:

constructor(){
    this.foo = bar
}

Dans le constructeur comme vous l'avez suggéré. Voir aussi le résumé du consensus .

ES7 et au-delà

Une nouvelle proposition pour ES7 est en cours d’élaboration, permettant des variables d’instance plus concises grâce aux déclarations et expressions de classe - https://esdiscuss.org/topic/es7-property-initializers

490
Benjamin Gruenbaum

Pour ajouter à la réponse de Benjamin, des variables de classe sont possibles, mais vous n’utiliseriez pas prototype pour les définir.

Pour une vraie variable de classe, vous voudriez faire quelque chose comme ceci:

_class MyClass {}
MyClass.foo = 'bar';
_

Depuis une méthode de classe, cette variable est accessible en tant que _this.constructor.foo_ (ou _MyClass.foo_).

Ces propriétés de classe ne sont généralement pas accessibles depuis l'instance de classe. c'est-à-dire _MyClass.foo_ donne _'bar'_ mais new MyClass().foo est undefined

Si vous souhaitez également avoir accès à votre variable de classe à partir d'une instance, vous devez également définir un getter:

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

MyClass.foo = 'bar';
_

Je n'ai testé cela qu'avec Traceur, mais je pense que cela fonctionnera de la même manière dans une implémentation standard.

JavaScript n'a pas vraiment de classes . Même avec ES6, nous recherchons un langage basé sur un objet ou un prototype plutôt qu'un langage basé sur une classe. Dans toute function X () {}, X.prototype.constructor pointe vers X. Lorsque l'opérateur new est utilisé sur X, un nouvel objet héritant de _X.prototype_ est créé. Toutes les propriétés non définies de ce nouvel objet (y compris constructor) sont recherchées à partir de là. Nous pouvons penser à cela en générant des propriétés d'objet et de classe.

122
lyschoening

Babel supporte les variables de classe dans ESNext, vérifiez ceci exemple :

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'
25
Kosmetika

Dans votre exemple:

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

Parce que MY_CONST est primitif https://developer.mozilla.org/en-US/docs/Glossary/Primitive nous pouvons simplement faire:

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

Mais si MY_CONST est un type de référence comme static get MY_CONST() {return ['string'];}, la sortie de l'alerte est chaîne, false . Dans un tel cas, l'opérateur delete peut faire l'affaire:

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

Et enfin pour la variable de classe pas 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
24
Oleg Mazko

Étant donné que votre problème est essentiellement stylistique (vous ne voulez pas remplir le constructeur avec un tas de déclarations), vous pouvez également le résoudre stylistiquement.

À mon avis, dans de nombreux langages de classe, le constructeur est une fonction nommée d'après le nom de la classe elle-même. Stylistiquement, nous pourrions utiliser cela pour créer une classe ES6 qui a toujours un sens, mais ne regroupe pas les actions typiques se déroulant dans le constructeur avec toutes les déclarations de propriété que nous effectuons. Nous utilisons simplement le constructeur JS réel comme "zone de déclaration", puis nous créons une fonction nommée classe que nous traitons autrement comme la zone "autre constructeur" en l'appelant à la fin du constructeur réel.

 "use strict"; 
 
 class MyClass 
 {
 // ne déclarez que vos propriétés, puis appelez this.ClassName (); à partir d'ici 
 constructeur () {
 this.prop1 = 'blah 1'; 
 this.prop2 = 'blah 2'; 
 this.prop3 = 'blah 3 '; 
 This.MyClass (); 
} 
 
 // toutes sortes d'autres choses "constructeurs", qui ne sont plus mêlées à des déclarations 
 MyClass () {
 DoW Any (); 
} 
} 

Les deux seront appelés lors de la construction de la nouvelle instance.

Un peu comme si vous aviez 2 constructeurs où vous séparez les déclarations et les autres actions de constructeurs que vous voulez effectuer, et stylistiquement, il n’est pas trop difficile de comprendre que c’est ce qui se passe aussi.

Je trouve que c'est un style agréable à utiliser lorsqu'il s'agit de traiter de nombreuses déclarations et/ou de nombreuses actions devant se produire lors de l'instanciation et dans le souci de garder les deux idées distinctes.


NOTE: Je n'utilise pas très volontiers les idées idiomatiques typiques d'initialisation (comme une méthode init() ou initialize()) car celles-ci sont souvent utilisées différemment. Il y a une sorte de différence présumée entre l'idée de construire et d'initialiser. En travaillant avec des constructeurs, les utilisateurs savent qu'ils sont appelés automatiquement dans le cadre de l'instanciation. Voir une méthode init que beaucoup de gens vont supposer sans un second regard qu'ils doivent faire quelque chose sous la forme de var mc = MyClass(); mc.init();, parce que c'est comme ça qu'on initialise généralement. Je n'essaie pas d'ajouter un processus d'initialisation pour l'utilisateur de la classe, j'essaie d'ajouter à le processus de construction de la classe elle-même.

Bien que certaines personnes puissent faire une double prise pendant un moment, c'est en fait le problème: elles leur disent que l'intention fait partie de la construction, même si cela les oblige à faire un peu une double prise et à partir "ce n'est pas comment les constructeurs ES6 fonctionnent "et prenons une seconde en regardant le constructeur actuel pour aller" oh, ils l'appellent en bas, je vois ", c'est bien mieux que de NE PAS communiquer cette intention (ou de la communiquer de manière incorrecte) et probablement obtenir beaucoup de les personnes l'utilisant mal, en essayant de l'initialiser de l'extérieur et de la malbouffe. C'est très intentionnel au modèle que je suggère.


Pour ceux qui ne veulent pas suivre ce modèle, l'inverse peut également fonctionner. Transformez les déclarations en une autre fonction au début. Peut-être nommez-le "propriétés" ou "publicProperties" ou quelque chose. Placez ensuite le reste dans le constructeur normal.

 "use strict"; 
 
 class MyClass 
 {
 properties () {
 this.prop1 = 'blah 1' ; 
 this.prop2 = 'blah 2'; 
 this.prop3 = 'blah 3'; 
} 
 
 constructeur () {
 this.properties (); 
 doWhats (); 
} 
} 

Notez que cette seconde méthode peut sembler plus propre, mais elle a aussi un problème inhérent où properties est remplacé comme une classe utilisant cette méthode en étend une autre. Vous devriez donner plus de noms uniques à properties pour éviter cela. Ma première méthode n'a pas ce problème car sa fausse moitié du constructeur porte uniquement le nom de la classe.

17
Jimbo Jonny

Qu'en est-il de la manière oldschool?

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

Dans le constructeur, vous ne mentionnez que les variables qui doivent être calculées. J'aime les héritages de prototypes pour cette fonctionnalité - cela peut aider à économiser beaucoup de mémoire (au cas où il y aurait beaucoup de vars jamais assignés).

14
zarkone

Comme Benjamin l'a dit dans sa réponse, TC39 a explicitement décidé de ne pas inclure cette fonctionnalité, du moins pour ES2015. Cependant, le consensus semble être de l’ajouter dans ES2016.

La syntaxe n'a pas encore été décidée, mais il existe une proposition préliminaire pour ES2016 qui vous permettra de déclarer des propriétés statiques sur une classe.

Grâce à la magie de babel, vous pouvez l'utiliser aujourd'hui. Activez la transformation des propriétés de classe conformément à ces instructions et vous êtes prêt à partir. Voici un exemple de syntaxe:

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

Cette proposition est dans un état très précoce, alors préparez-vous à modifier votre syntaxe à mesure que le temps passe.

13
BonsaiOak

[Long fil, je ne sais pas si c'est déjà indiqué comme option ...].
Une simple alternative pour niquement des constants, définirait le const en dehors de la classe. Ce ne sera accessible que depuis le module lui-même, à moins d'être accompagné d'un getter.
De cette façon, prototype n'est pas encombré et vous obtenez le 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);
    }
}
9
Hertzel Guinness

Vous pouvez imiter le comportement des classes es6 ... et utiliser vos variables de classe :)

Regarde maman ... pas de cours!

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

Je l'ai mis sur GitHub

5
Ruslan

ES7 syntaxe du membre de la classe:

ES7 a une solution pour "jongler" avec votre fonction constructeur. Voici un exemple:

class Car {
  
  wheels = 4;
  weight = 100;

}

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

L'exemple ci-dessus ressemblerait à ceci dans ES6:

class Car {

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

}

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

Sachez que cette syntaxe peut ne pas être prise en charge par tous les navigateurs et doit être transpilée dans une version antérieure de JS.

Bonus: une fabrique d'objets:

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

La façon dont j'ai résolu ce problème, ce qui est une autre option (si vous avez jQuery disponible), était de définir les champs dans un objet old-school, puis d'étendre la classe avec cet objet. Je ne voulais pas non plus associer le constructeur à des tâches, cela semblait être une solution simple.

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

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

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

- Mise à jour à la suite du commentaire de Bergi.

Aucune version de JQuery:

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

    }
}

Vous vous retrouvez toujours avec le constructeur 'fat', mais au moins tout est dans une classe et assigné en un hit.

EDIT # 2: J'ai maintenant bouclé la boucle et j'attribue maintenant des valeurs au constructeur, par exemple.

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

Pourquoi? En réalité, c'est simple, en utilisant ce qui précède et quelques commentaires JSdoc, PHPStorm a été capable de compléter le code des propriétés. Nice a assigné tous les vars en un seul coup, mais l’impossibilité de coder les propriétés complètes, à mon avis, ne vaut pas l’avantage (presque certainement minuscule) en termes de performances.

0
Steve Childs

Eh bien, vous pouvez déclarer des variables à l'intérieur du constructeur.

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

var foo = new Foo()

foo.method()
0
Osama Xäwãñz