it-swarm.com.de

ES6-Klassenvariablenalternativen

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

// ES 5
FrameWork.Class({

    variable: 'string',
    variable2: true,

    init: function(){

    },

    addItem: function(){

    }

});

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

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

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

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

Ich habe über viele Möglichkeiten nachgedacht, um mit diesem Problem umzugehen, aber noch keine guten gefunden. (Beispiel: Erstellen Sie einen ClassConfig -Handler und übergeben Sie ein parameter -Objekt, das separat von der Klasse deklariert wird. Dann würde der Handler der Klasse eine Verknüpfung hinzufügen. Ich habe auch über WeakMaps nachgedacht irgendwie zu integrieren.)

Welche Ideen müssten Sie für diese Situation haben?

468
wintercounter

Update 2018:

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

In der Zwischenzeit kann jeder, der TypeScript oder babel verwendet, die folgende Syntax verwenden:

varName = value

Innerhalb einer Klassendeklaration/eines Ausdruckskörpers wird eine Variable definiert. Hoffentlich kann ich in ein paar Monaten/Wochen ein Update veröffentlichen.

Update: Chrome 74 wird jetzt mit dieser Syntax geliefert.


Die Notizen im ES-Wiki für den Vorschlag in ES6 ( maximal minimale Klassen ) beachten:

Es gibt (absichtlich) keine direkte deklarative Methode zum Definieren von Klasseneigenschaften für Prototypdaten (außer Methoden) oder Instanzeigenschaften

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

In einer Klassendefinition angegebene Eigenschaften erhalten dieselben Attribute wie in einem Objektliteral.

Dies bedeutet, dass das, wonach Sie fragen, berücksichtigt und ausdrücklich dagegen entschieden wurde.

aber wieso?

Gute Frage. Die guten Leute von TC39 wollen, dass Klassendeklarationen die Fähigkeiten einer Klasse deklarieren und definieren. Nicht seine Mitglieder. Eine ES6-Klassendeklaration definiert ihren Vertrag für den Benutzer.

Denken Sie daran, dass eine Klassendefinition Prototyp-Methoden definiert. Das Definieren von Variablen auf dem Prototyp ist im Allgemeinen nicht Ihre Aufgabe. Sie können natürlich verwenden:

constructor(){
    this.foo = bar
}

Im Konstruktor, wie Sie vorgeschlagen haben. Siehe auch die Zusammenfassung des Konsenses .

ES7 und darüber hinaus

Es wird an einem neuen Vorschlag für ES7 gearbeitet, der präzisere Instanzvariablen durch Klassendeklarationen und -ausdrücke ermöglicht - https://esdiscuss.org/topic/es7-property-initializers

490

Nur um Benjamins Antwort zu ergänzen - Klassenvariablen sind möglich, aber Sie würden nicht prototype verwenden, um sie festzulegen.

Für eine echte Klassenvariable möchten Sie etwa Folgendes tun:

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

Innerhalb einer Klassenmethode kann auf diese Variable als _this.constructor.foo_ (oder _MyClass.foo_) zugegriffen werden.

Auf diese Klasseneigenschaften kann die Klasseninstanz normalerweise nicht zugreifen. d.h. _MyClass.foo_ gibt _'bar'_, aber new MyClass().foo ist undefined

Wenn Sie auch von einer Instanz aus Zugriff auf Ihre Klassenvariable haben 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, dass es in einer Standardimplementierung genauso funktionieren wird.

JavaScript hat nicht wirklich Klassen . Selbst mit ES6 betrachten wir eine objekt- oder prototypbasierte Sprache anstelle einer klassenbasierten Sprache. In jedem function X () {} zeigt X.prototype.constructor zurück auf X. Wenn der Operator new für X verwendet wird, wird ein neues Objekt erstellt, das _X.prototype_ übernimmt. Alle undefinierten Eigenschaften in diesem neuen Objekt (einschließlich constructor) werden von dort aus nachgeschlagen. Wir können uns das als Erzeugung von Objekt- und Klasseneigenschaften vorstellen.

122
lyschoening

Babel unterstützt Klassenvariablen in ESNext, überprüfen Sie dies 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'
25
Kosmetika

In deinem Beispiel:

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

Da MY_CONST primitiv ist https://developer.mozilla.org/en-US/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

Wenn jedoch MY_CONST ein Referenztyp wie static get MY_CONST() {return ['string'];} ist, lautet die Alarmausgabe string, false . In diesem Fall kann der Operator delete Folgendes tun:

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 Klassenvariable nicht 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

Da Ihr Problem meist stilistisch ist (Sie möchten den Konstruktor nicht mit einer Reihe von Deklarationen füllen), kann es auch stilistisch gelöst werden.

So wie ich es sehe, muss der Konstruktor in vielen klassenbasierten Sprachen eine Funktion sein, die nach dem Klassennamen selbst benannt ist. Stilistisch könnten wir das verwenden, um eine ES6-Klasse zu erstellen, die zwar stilistisch noch sinnvoll ist, die typischen Aktionen, die im Konstruktor stattfinden, jedoch nicht mit allen von uns ausgeführten Eigenschaftsdeklarationen gruppiert. Wir verwenden einfach den tatsächlichen JS-Konstruktor als "Deklarationsbereich" und erstellen dann eine Klasse mit dem Namen function, die wir ansonsten als "other constructor stuff" behandeln und am Ende des wahren Konstruktors aufrufen.

 "use strict"; 
 
 class MyClass 
 {
 // deklariere nur deine Eigenschaften und rufe dann this.ClassName () auf; von hier 
 Konstruktor () {
 this.prop1 = 'blah 1'; 
 this.prop2 = 'blah 2'; 
 this.prop3 = 'blah 3 '; 
 This.MyClass (); 
} 
 
 // alle möglichen anderen "Konstruktor" -Stoffe, nicht mehr mit Deklarationen durcheinander 
 MyClass () {
 DoWhatever (); 
} 
} 

Beide werden aufgerufen, wenn die neue Instanz erstellt wird.

Sorta mag es, zwei Konstruktoren zu haben, in denen Sie die Deklarationen und die anderen Konstruktoraktionen, die Sie ausführen möchten, trennen, und stilistisch ist es nicht allzu schwer zu verstehen, dass dies auch der Fall ist.

Ich finde, dass es ein netter Stil ist, wenn es um viele Deklarationen und/oder viele Aktionen geht, die bei der Instanziierung ausgeführt werden müssen, und wenn die beiden Ideen voneinander unterschieden werden sollen.


NOTE: Ich verwende sehr bewusst nicht die typischen Redewendungen des "Initialisierens" (wie eine init() oder initialize() Methode), da diese oft unterschiedlich verwendet werden. Es gibt eine Art vermuteten Unterschied zwischen der Idee des Konstruierens und Initialisierens. Durch die Arbeit mit Konstruktoren wissen die Leute, dass sie im Rahmen der Instanziierung automatisch aufgerufen werden. Wenn Sie eine init -Methode sehen, werden viele Leute ohne einen zweiten Blick annehmen, dass sie etwas in der Form von var mc = MyClass(); mc.init(); tun müssen, weil Sie dies normalerweise so initialisieren. Ich versuche nicht, einen Initialisierungsprozess für den Benutzer der Klasse hinzuzufügen, sondern füge to den Konstruktionsprozess der Klasse selbst hinzu.

Während manche Leute für einen Moment ein Double-Take machen, ist das eigentlich der springende Punkt: Es teilt ihnen mit, dass die Absicht Teil des Aufbaus ist, auch wenn sie dadurch ein Double-Take-and-Go machen müssen, was nicht der Fall ist Wie ES6-Konstruktoren funktionieren "und schauen Sie sich den eigentlichen Konstruktor noch einmal an" oh, sie nennen es unten, wie ich sehe ", das ist weitaus besser, als diese Absicht NICHT zu kommunizieren (oder sie falsch zu kommunizieren) und wahrscheinlich eine Menge davon zu bekommen Leute, die es falsch benutzen, versuchen es von außen zu initialisieren und Junk. 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. Verarbeiten Sie die Deklarationen zu Beginn in einer anderen Funktion. Nennen Sie es vielleicht "properties" oder "publicProperties" oder so. Dann lege den Rest des Zeugs in den normalen Konstruktor.

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

Beachten Sie, dass diese zweite Methode möglicherweise sauberer aussieht, aber auch ein inhärentes Problem aufweist, bei dem properties überschrieben wird, wenn eine Klasse, die diese Methode verwendet, eine andere Klasse erweitert. Sie müssten properties eindeutigere Namen geben, um dies zu vermeiden. Meine erste Methode hat dieses Problem nicht, da die gefälschte Hälfte des Konstruktors eindeutig nach der Klasse benannt ist.

17
Jimbo Jonny

Was ist mit der alten Schule?

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

Im Konstruktor erwähnen Sie nur die Variablen, die berechnet werden müssen. Ich mag die Vererbung von Prototypen für diese Funktion - es kann helfen, viel Speicherplatz zu sparen (falls es viele nicht zugewiesene Variablen gibt).

14
zarkone

Wie Benjamin in seiner Antwort ausführte, hat TC39 ausdrücklich beschlossen, diese Funktion zumindest für ES2015 nicht zu verwenden. Der Konsens scheint jedoch zu sein, dass sie es in ES2016 hinzufügen werden.

Die Syntax wurde noch nicht festgelegt, aber es gibt ein vorläufiger Vorschlag für ES2016, mit dem Sie statische Eigenschaften für eine Klasse deklarieren können.

Dank der Magie von Babel können Sie dies heute nutzen. Aktivieren Sie die Klasseneigenschaftentransformation gemäß diesen Anweisungen und Sie können loslegen. Hier ist ein Beispiel für die Syntax:

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

Dieser Vorschlag befindet sich in einem sehr frühen Stadium. Seien Sie also darauf vorbereitet, Ihre Syntax im Laufe der Zeit zu optimieren.

13
BonsaiOak

[Langer Thread, nicht sicher, ob er bereits als Option aufgeführt ist ...].
Eine einfache Alternative für nur Konkurrenten würde die Konstante außerhalb der Klasse definieren. Dies ist nur über das Modul selbst möglich, es sei denn, es wird ein Getter mitgeliefert.
Auf diese Weise wird prototype nicht verschmutzt und Sie erhalten 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

Sie können das Verhalten von es6-Klassen nachahmen ... und Ihre Klassenvariablen verwenden :)

Schau Mama ... keine Klassen!

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

Ich habe es auf GitHub gesetzt

5
Ruslan

ES7 Syntax der Klassenmitglieder:

ES7 hat eine Lösung, mit der Sie Ihre Konstruktorfunktion überladen können. Hier ist ein Beispiel:

class Car {
  
  wheels = 4;
  weight = 100;

}

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

Das obige Beispiel würde in ES6 wie folgt aussehen:

class Car {

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

}

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

Beachten Sie dabei, dass diese Syntax möglicherweise nicht von allen Browsern unterstützt wird und möglicherweise eine frühere Version von JS transpiliert werden muss.

Bonus: eine Objektfabrik:

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

Die Art und Weise, wie ich das gelöst habe, was eine andere Option ist (wenn Sie jQuery zur Verfügung haben), war, die Felder in einem Objekt der alten Schule zu definieren und dann die Klasse mit diesem Objekt zu erweitern. Ich wollte den Konstruktor auch nicht mit Aufträgen überhäufen, dies schien eine ordentliche Lösung zu sein.

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

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

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

- Update Nach Bergis Kommentar.

Keine JQuery-Version:

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

    }
}

Sie haben zwar immer noch einen "fetten" Konstruktor, aber zumindest ist dies alles in einer Klasse und wird in einem Treffer zugewiesen.

EDIT # 2: Ich habe jetzt den Kreis geschlossen und weise jetzt Werte im Konstruktor zu, z.

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

Warum? Im Grunde genommen war PHPStorm in der Lage, mithilfe der obigen Informationen und einiger JSdoc-Kommentare Code für die Eigenschaften fertigzustellen. Das Zuweisen aller Variablen auf einen Schlag war nett, aber die Unfähigkeit, die Eigenschaften zu vervollständigen, ist den (mit ziemlicher Sicherheit winzigen) Leistungsvorteil nicht wert.

0
Steve Childs

Nun, Sie können Variablen innerhalb des Konstruktors deklarieren.

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

var foo = new Foo()

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