547 Stimmen

ES6 Klassenvariablen-Alternativen

Aktuell verwenden viele von uns in ES5 das folgende Muster in Frameworks, um Klassen und Klassenvariablen zu erstellen, was bequem ist:

// ES 5
FrameWork.Class({

    variable: 'string',
    variable2: true,

    init: function(){

    },

    addItem: function(){

    }

});

In ES6 können Klassen nativ erstellt werden, es gibt jedoch keine Option für Klassenvariablen:

// ES6
class MyClass {
    const MY_CONST = 'string'; // <-- dies ist in ES6 nicht möglich
    constructor(){
        this.MY_CONST;
    }
}

Leider funktioniert das oben genannte nicht, da Klassen nur Methoden enthalten können.

Ich verstehe, dass ich this.myVar = true im constructor verwenden kann ... aber ich möchte meinen Konstruktor nicht 'verunreinigen', besonders wenn ich 20-30+ Parameter für eine größere Klasse habe.

Ich habe über viele Möglichkeiten nachgedacht, dieses Problem zu lösen, aber noch keine guten gefunden. (Zum Beispiel: Erstellen eines ClassConfig-Handlers und Übergeben eines parameter-Objekts, das separat von der Klasse deklariert wird. Dann würde der Handler an die Klasse angehängt werden. Ich habe auch über die Integration von WeakMaps nachgedacht.)

Welche Ideen hätten Sie, um mit dieser Situation umzugehen?

561voto

Benjamin Gruenbaum Punkte 259076

Update 2018:

Es gibt jetzt einen Stage 3 Vorschlag - ich freue mich darauf, diese Antwort in ein paar Monaten überflüssig zu machen.

In der Zwischenzeit können alle, die TypeScript oder Babel verwenden, die Syntax verwenden:

varName = Wert

In einer Klassendeklaration/-ausdruck und es wird eine Variable definieren. Hoffentlich werde ich in ein paar Monaten/Wochen ein Update posten können.

Update: Chrome 74 wird jetzt mit dieser Syntax ausgeliefert.


Die Notizen im ES-Wiki zum Vorschlag in ES6 (maximal minimale Klassen) besagen:

Es gibt (absichtlich) keine direkte deklarative Möglichkeit, entweder Prototypdateneigenschaften (außer Methoden), Klasseneigenschaften oder Instanzproperty zu definieren.

Klasseneigenschaften und Prototypdateneigenschaften müssen außerhalb der Deklaration erstellt werden.

In einer Klassendefinition zugewiesene Eigenschaften haben dieselben Attribute wie wenn sie in einem Objektliteral erscheinen würden.

Dies bedeutet, dass was du verlangst, berücksichtigt wurde und ausdrücklich abgelehnt wurde.

aber... warum?

Gute Frage. Die Mitglieder von TC39 möchten, dass Klassendeklarationen die Fähigkeiten einer Klasse deklarieren und definieren. Nicht ihre Mitglieder. Eine ES6 Klassendeklaration definiert ihren Vertrag für den Benutzer.

Vergiss nicht, dass eine Klassendefinition Prototyp Methoden definiert - Variablen im Prototyp zu definieren ist im Allgemeinen nichts, was man tut. Du kannst natürlich auch verwenden:

constructor(){
    this.foo = bar
}

Im Konstruktor, wie du vorgeschlagen hast. Siehe auch die Zusammenfassung des Konsenses.

ES7 und darüber hinaus

Ein neuer Vorschlag für ES7 wird erarbeitet, der eine präzisere Instanzvariablen durch Klassendeklarationen und -ausdrücke ermöglicht - https://esdiscuss.org/topic/es7-property-initializers

141voto

lyschoening Punkte 17420

Nur um Benjamins Antwort hinzuzufügen - Klassenvariablen sind möglich, aber Sie würden nicht prototype verwenden, um sie zu setzen.

Für eine echte Klassenvariable möchten Sie etwas Ähnliches wie das Folgende tun:

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

Von innerhalb einer Klassenmethode aus kann auf diese Variable als this.constructor.foo (oder MyClass.foo) zugegriffen werden.

Diese Klassenvariablen wären normalerweise nicht von der Klasseninstanz aus zugänglich. d.h. MyClass.foo gibt 'bar' zurück, aber new MyClass().foo ist undefined

Wenn Sie auch von einer Instanz aus auf Ihre Klassenvariable zugreifen möchten, müssen Sie zusätzlich einen Getter definieren:

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

MyClass.foo = 'bar';

Ich habe dies nur mit Traceur getestet, aber ich glaube, es wird genauso in einer Standardimplementierung funktionieren.

JavaScript hat eigentlich keine Klassen. Selbst mit ES6 handelt es sich bei der Sprache eher um eine objekt- oder prototypenbasierte Sprache als um eine klassenbasierte Sprache. In jeder function X () {} zeigt X.prototype.constructor wieder zurück auf X. Wenn der new-Operator auf X angewendet wird, wird ein neues Objekt erstellt, das X.prototype erbt. Alle undefinierten Eigenschaften in diesem neuen Objekt (einschließlich constructor) werden von dort aus aufgerufen. Wir können dies als Generierung von Objekt- und Klassenattributen betrachten.

33voto

Kosmetika Punkte 20724

Babel unterstützt Klassenvariablen in ESNext, überprüfen Sie dieses Beispiel:

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'

32voto

Oleg Mazko Punkte 1510

In Ihrem Beispiel:

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

Weil MY_CONST primitiv ist https://developer.mozilla.org/de/docs/Glossary/Primitive können wir einfach folgendes tun:

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

Aber wenn MY_CONST ein Referenztyp ist wie static get MY_CONST() {return ['string'];} dann ist die Ausgabe des Alerts string, false. In einem solchen Fall kann der delete Operator helfen:

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

Und schließlich für Klassenvariablen, die nicht const sind:

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

19voto

Jimbo Jonny Punkte 3391

Da Ihr Problem hauptsächlich stilistischer Natur ist (nicht den Konstruktor mit einer Vielzahl von Deklarationen füllen möchten), kann es auch stilistisch gelöst werden.

Meiner Ansicht nach haben viele Klassen basierte Sprachen den Konstruktor als eine Funktion mit dem Namen der Klassen selbst. Stilistisch könnten wir das verwenden, um eine ES6-Klasse zu erstellen, die stilistisch immer noch Sinn macht, aber die typischen Aktionen, die im Konstruktor stattfinden, nicht mit all den Eigenschaftsdeklarationen gruppieren, die wir machen. Wir verwenden einfach den tatsächlichen JS-Konstruktor als "Deklarationsbereich" und erstellen dann eine Klassen benannte Funktion, die wir sonst als "anderer Konstruktorbereich" behandeln, und rufen sie am Ende des wirklichen Konstruktors auf.

"use strict";

class MyClass
{
    // Deklarieren Sie nur Ihre Eigenschaften und rufen Sie this.ClassName(); von hier aus auf
    constructor(){
        this.prop1 = 'blah 1';
        this.prop2 = 'blah 2';
        this.prop3 = 'blah 3';
        this.MyClass();
    }

    // Alle möglichen anderen "Konstruktor" Aufgaben, nicht mehr mit Deklarationen vermischt
    MyClass() {
        doWhatever();
    }
}

Beide werden aufgerufen, wenn die neue Instanz erstellt wird.

So ähnlich wie zwei Konstruktoren zu haben, in denen Sie die Deklarationen und die anderen Konstruktoraktionen, die Sie ausführen möchten, trennen, und stilistisch macht es auch nicht zu schwer zu verstehen, was dabei vor sich geht.

Ich finde es einen schönen Stil, wenn man es mit vielen Deklarationen und/oder vielen Aktionen zu tun hat, die beim Instanziieren ausgeführt werden müssen, und die beiden Ideen voneinander getrennt halten möchte.


HINWEIS: Ich verwende absichtlich nicht die typischen idiomatischen Ideen von "Initialisierung" (wie eine init() oder initialize() Methode), weil diese oft unterschiedlich verwendet werden. Es gibt eine Art unterstellter Unterschied zwischen der Idee des Konstruierens und der Initialisierung. Wenn man mit Konstruktoren arbeitet, weiß man, dass sie automatisch als Teil der Instanziierung aufgerufen werden. Wenn man eine init Methode sieht, werden viele Menschen ohne genaues Hinsehen davon ausgehen, dass sie etwas in der Form von var mc = MyClass(); mc.init(); tun müssen, weil Sie normalerweise initialisieren. Ich versuche keinen Initialisierungsprozess für den Benutzer der Klasse hinzuzufügen, sondern ich versuche, ihn an den Konstruktionsprozess der Klasse selbst anzufügen.

Obwohl manche Leute vielleicht einen Moment lang zweifeln, das ist eigentlich der eigentliche Punkt: Es kommuniziert ihnen, dass die Absicht Teil der Konstruktion ist, auch wenn sie einen kurzen Moment innehalten und sagen "so funktionieren ES6 Konstruktoren nicht" und dann einen zweiten Blick auf den tatsächlichen Konstruktor werfen und sagen "oh, sie rufen ihn unten auf, ich verstehe", das ist viel besser als die Absicht nicht zu kommunizieren (oder sie falsch zu kommunizieren) und wahrscheinlich viele Menschen dazu zu bringen, sie falsch zu verwenden, sie von außen zu initialisieren und so weiter. Das ist sehr beabsichtigt für das Muster, das ich vorschlage.


Für diejenigen, die diesem Muster nicht folgen möchten, kann auch das genaue Gegenteil funktionieren. Lassen Sie die Deklarationen am Anfang in eine andere Funktion auslagern. Vielleicht nennen Sie es "Eigenschaften" oder "publicProperties" oder so etwas. Dann setzen Sie den Rest der Dinge in den normalen Konstruktor.

"use strict";

class MyClass
{
    properties() {
        this.prop1 = 'blah 1';
        this.prop2 = 'blah 2';
        this.prop3 = 'blah 3';
    }

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

Beachten Sie, dass diese zweite Methode zwar sauberer aussehen kann, aber auch ein inhärentes Problem hat, bei dem properties überschrieben wird, wenn eine Klasse, die diese Methode verwendet, eine andere erweitert. Sie müssten properties eindeutigere Namen geben, um das zu vermeiden. Meine erste Methode hat dieses Problem nicht, weil ihr falscher Teil des Konstruktors nach der Klasse eindeutig benannt ist.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X