Alternatywy zmiennych klasy ES6

Obecnie w ES5 wielu z nas używa następującego wzorca w frameworkach do tworzenia klas i zmiennych klas, co jest wygodne:

// ES 5
FrameWork.Class({

    variable: 'string',
    variable2: true,

    init: function(){

    },

    addItem: function(){

    }

});

W ES6 można tworzyć klasy natywnie, ale nie ma opcji, aby mieć zmienne klasy:

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

Niestety powyższe nie zadziała, ponieważ klasy mogą zawierać tylko metody.

Rozumiem, że mogę this.myVar = true w constructor...ale nie chcę "wyrzucać" mojego konstruktora, zwłaszcza gdy mam 20-30 + paramów dla większej klasy.

Byłem myśląc o wielu sposobach radzenia sobie z tym problemem, ale nie znalazłem jeszcze żadnych dobrych. (Na przykład: utwórz obsługę ClassConfig i przekaż obiekt parameter, który jest zadeklarowany oddzielnie od klasy. Wtedy opiekun przywiązuje się do klasy. Myślałem o WeakMaps również, aby jakoś zintegrować.)

Jaki rodzaj pomysłów musiałbyś poradzić sobie z tą sytuacją?

Author: Zearin, 2014-03-20

13 answers

2018 Aktualizacja:

Jest teraz wniosek z etapu 3 - oczekuję, że ta odpowiedź stanie się nieaktualna w ciągu kilku miesięcy.

W międzyczasie każdy, kto używa TypeScript lub babel może używać składni:

varName = value

Wewnątrz ciała deklaracji/wyrażenia klasy i zdefiniuje zmienną. Mam nadzieję,że za kilka miesięcy / tygodni będę mógł opublikować aktualizację.


Uwagi w Es wiki do propozycji w ES6 (maximally minimal classes) Uwaga:

Nie ma (celowo) bezpośredniego deklaratywnego sposobu definiowania właściwości danych prototypowych (innych niż metody) właściwości klasy, ani właściwości instancji

Właściwości klasy i właściwości danych prototypu muszą być utworzone poza deklaracją.

Właściwości określone w definicji klasy mają przypisane te same atrybuty, jak gdyby pojawiły się w dosłownym obiekcie.

Oznacza to, że to, o co prosisz, zostało rozważone i wyraźnie postanowione przeciw.

Ale... dlaczego? Dobre pytanie. Dobrzy ludzie z TC39 chcą, aby deklaracje klas deklarowały i definiowały możliwości klasy. Nie jego członkowie. Deklaracja klasy ES6 definiuje jej kontrakt dla użytkownika.

Pamiętaj, że definicja klasy definiuje metody prototype - definiowanie zmiennych w prototypie zazwyczaj nie jest czymś, co robisz. Można oczywiście użyć:

constructor(){
    this.foo = bar
}

W konstruktorze, jak sugerowałeś. Zobacz też podsumowanie konsensusu .

ES7 i dalej

Nowa propozycja dla ES7 jest obecnie opracowywana, która umożliwia bardziej zwięzłe zmienne instancji poprzez deklaracje klas i wyrażenia - https://esdiscuss.org/topic/es7-property-initializers

 444
Author: Benjamin Gruenbaum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-10 09:02:08

Wystarczy dodać do answer - class zmienne są możliwe, ale nie użyjesz prototype, aby je ustawić.

Dla zmiennej klasy true powinieneś zrobić coś takiego:

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

Z poziomu metody klasy, do której można uzyskać dostęp jako this.constructor.foo (lub MyClass.foo).

Te właściwości klasy zwykle nie będą dostępne z instancji klasy. tj. MyClass.foo daje 'bar' ale new MyClass().foo jest undefined

Jeśli chcesz mieć również dostęp do swojej klasy zmienna z instancji, musisz dodatkowo zdefiniować getter:

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

MyClass.foo = 'bar';

Testowałem to tylko z Traceur, ale wierzę, że będzie działać tak samo w standardowej implementacji.

JavaScript tak naprawdę nie ma klas . Nawet w przypadku ES6 patrzymy na język oparty na obiektach lub prototypach, a nie na języku klasowym. W każdym function X () {}, X.prototype.constructor punkty do X. Kiedy new operator jest używany na X, nowy obiekt jest created inheriting X.prototype. Wszystkie niezdefiniowane właściwości W tym nowym obiekcie (łącznie z constructor) są stamtąd sprawdzane. Możemy o tym myśleć jako o generowaniu właściwości obiektu i klasy.

 109
Author: lyschoening,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-06 09:31:14

W twoim przykładzie:

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

Ponieważ MY_CONST jest prymitywny https://developer.mozilla.org/en-US/docs/Glossary/Primitive możemy po prostu zrobić:

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

Ale jeśli MY_CONST jest typem referencyjnym, jak static get MY_CONST() {return ['string'];} Wyjście alarmowe to string, false . W takim przypadku delete operator może wykonać sztuczkę:

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

I wreszcie dla zmiennej klasy nie 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
Author: Oleg Mazko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-15 07:35:21

Babel obsługuje zmienne klasy w ESNext, sprawdź ten przykład :

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'
 20
Author: Kosmetika,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-15 18:58:02

Ponieważ twój problem jest głównie stylistyczny (nie chcąc wypełniać konstruktora kilkoma deklaracjami), można go rozwiązać również stylistycznie.

Jak to widzę, wiele języków opartych na klasach ma konstruktor będący funkcją nazwaną po samej nazwie klasy. Stylistycznie moglibyśmy użyć tego, aby stworzyć klasę ES6, która stylistycznie nadal ma sens, ale nie grupuje typowych działań zachodzących w konstruktorze ze wszystkimi deklaracjami własności, które robimy. My po prostu użyj rzeczywistego konstruktora JS jako "obszaru deklaracji", a następnie stwórz klasę nazwaną funkcją, którą w przeciwnym razie traktujemy jako obszar" innych konstruktorów", nazywając go na końcu prawdziwego konstruktora.

"use strict";

class MyClass
{
    // only declare your properties and then call this.ClassName(); from here
    constructor(){
        this.prop1 = 'blah 1';
        this.prop2 = 'blah 2';
        this.prop3 = 'blah 3';
        this.MyClass();
    }

    // all sorts of other "constructor" stuff, no longer jumbled with declarations
    MyClass() {
        doWhatever();
    }
}

Oba zostaną wywołane podczas konstruowania nowej instancji.

Tak jakby mieć 2 konstruktory, w których oddzielasz deklaracje i inne działania konstruktorów, które chcesz podjąć, i stylistycznie sprawia, że nie jest zbyt trudno zrozumieć, o co chodzi też.

Uważam, że jest to dobry styl do użycia, gdy mamy do czynienia z wieloma deklaracjami i / lub wieloma działaniami, które muszą się wydarzyć na instancjacji i chcą zachować te dwa pomysły odmienne od siebie.


Uwaga: bardzo celowo nie używam typowych idiomatycznych idei "inicjalizacji" (jak Metoda init() lub initialize()), ponieważ często są one używane inaczej. Istnieje pewna domniemana różnica między ideą konstruowania i inicjowania. Praca z konstruktorzy ludzie wiedzą, że są wywoływani automatycznie jako część instancji. Widząc metodę init Wiele osób bez drugiego spojrzenia założy, że musi robić coś w formie var mc = MyClass(); mc.init();, ponieważ w ten sposób zazwyczaj inicjuje się. Nie próbuję dodawać procesu inicjalizacji dla użytkownika klasy, próbuję dodać do proces budowy samej klasy.

Podczas gdy niektórzy mogą zrobić podwójne ujęcie przez chwilę, to właściwie chodzi o to, że komunikuje im, że intencja jest częścią budowy, nawet jeśli to sprawia, że robią trochę podwójne ujęcie i odejście "tak nie działa ES6 constructors" i poświęć sekundę patrząc na rzeczywisty konstruktor, aby przejść "oh, nazywają to na dole, widzę", to o wiele lepiej niż nie komunikowanie tego intencji (lub nieprawidłowo komunikując go) i prawdopodobnie coraz wiele osób używa go źle, próbując zainicjować go z zewnątrz i śmieci. To bardzo dużo. zamierzony do wzorca, który sugeruję.


Dla tych, którzy nie chcą podążać za tym wzorcem, może działać dokładnie odwrotnie. Na początku przekaż deklaracje do innej funkcji. Może nazwij to "properties" lub "publicProperties" czy coś. Następnie włóż resztę rzeczy do normalnego konstruktora.

"use strict";

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

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

Zauważ, że ta druga metoda może wyglądać na czystszą, ale ma również nieodłączny problem, w którym properties zostaje nadpisana, ponieważ jedna klasa używająca tej metody rozszerza kolejny. Musisz podać więcej unikalnych nazw properties, aby tego uniknąć. Moja pierwsza metoda nie ma tego problemu, ponieważ jej fałszywa połowa konstruktora jest unikalnie nazwana po klasie.

 13
Author: Jimbo Jonny,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-27 01:33:16

A co z oldschoolowym sposobem?

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

W konstruktorze wymieniasz tylko te zmienne, które muszą być obliczone. Lubię dziedziczenie prototypów dla tej funkcji-może pomóc zaoszczędzić dużo pamięci (w przypadku, gdy jest dużo nigdy nie przypisanych VAR-ów).

 12
Author: zarkone,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-10 12:33:32

Jak powiedział Benjamin w swojej odpowiedzi, TC39 wyraźnie postanowił nie włączać tej funkcji przynajmniej dla ES2015. Wydaje się jednak, że konsensus jest taki, że dodadzą go w ES2016.

Składnia nie została jeszcze ustalona, ale istnieje wstępna propozycja dla ES2016, która pozwoli Ci zadeklarować właściwości statyczne klasy.

Dzięki magii babel możesz z niej korzystać już dziś. Umożliwia przekształcenie właściwości klasy zgodnie z tymi instrukcjami i możesz iść. Oto przykład składni:
class foo {
  static myProp = 'bar'
  someFunction() {
    console.log(this.myProp)
  }
}

Ta propozycja jest w bardzo wczesnym stadium, więc przygotuj się na dostosowanie składni w miarę upływu czasu.

 11
Author: BonsaiOak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-17 12:19:30

Możesz naśladować zachowanie klas es6... i używaj zmiennych klasowych:)

Spójrz mamo... żadnych zajęć!
// 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);

I put it on GitHub

 5
Author: Ruslan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-29 02:36:51

[długi wątek, Nie wiem, czy jest już wymieniony jako opcja...].
Prosta alternatywa będzie definiowaniem const poza klasą. Będzie to dostępne tylko z samego modułu, chyba że towarzyszy mu getter.
W ten sposób {[1] }nie jest zaśmiecony i dostajesz 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);
    }
}
 5
Author: Hertzel Guinness,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-21 07:35:45

Czy można uniknąć całego problemu używając silnych liter i małej biblioteki logiki szablonów zawartych w większym zamknięciu?

Ignorowanie zamknięcia na razie

const myDynamicInputs=(items)=>\backtick -${ items.map((item, I, array)=>'${do tons of junk}').join('')}';

Http://codepen.io/jfrazz/pen/BQJPBZ/

To najprostszy przykład jaki mogę zaoferować z repozytorium, pierwsze 400 linii to biblioteka danych+ kilka podstawowych funkcji użytkowych. Plus garść stałych użytkowych.

Po płytce kotła, którą zamieniamy w dane uri -- pobrane przez użytkowników aplikacji -- mamy szablony tablic, które muszą zostać zniesione i przesunięte, ale które mogą być złożone z wejść, rozwijanych lub 52 stron pytań i danych.

To jest ten drugi przykład: zjedz obiekt, uzyskaj wejścia różnych typów, wszystkie używając const jako podstawowej zmiennej biblioteki, która jest konstruowana.

Http://codepen.io/jfrazz/pen/rWprVR/

Nie dokładnie to, o co prosiłeś, ale wyraźne pokazanie, że stała może być ładna dynamiczny.

 0
Author: Jason Frazzano,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-02 04:57:43

Sposób, w jaki rozwiązałem to, co jest inną opcją (jeśli masz jQuery dostępny), polegał na zdefiniowaniu pól w obiekcie old-school, a następnie rozszerzeniu klasy o ten obiekt. Nie chciałem też obrzucać konstruktora zadaniami, to wyglądało na zgrabne rozwiązanie.

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

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

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

-- Aktualizacja po komentarzu Bergi.

Brak Wersji JQuery:

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

    }
}

Nadal kończysz z konstruktorem 'fat', ale przynajmniej jego wszystko w jednej klasie i przypisane w jednym hit.

EDIT # 2: Teraz przeszedłem całe koło i teraz przypisuję wartości w konstruktorze, np.

class SavedSearch  {
    constructor() {
        this.id = '';
        this.type = '';
        this.title = '';
        this.createdAt = new Date();
    }
}
Dlaczego? Proste naprawdę, korzystając z powyższego i kilku komentarzy JSdoc, PHPStorm był w stanie wykonać uzupełnianie kodu na właściwościach. Przypisanie wszystkich VAR-ów w jednym trafieniu było miłe, ale niemożność kodowania dopełnienia właściwości, imo, nie jest warta (prawie na pewno minuskuła) korzyści z wydajności.
 0
Author: Steve Childs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-17 22:41:18

Cóż, możesz zadeklarować zmienne wewnątrz konstruktora.

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

var foo = new Foo()

foo.method()
 0
Author: Osama Xäwãñz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-21 08:17:02

To jest trochę hackish combo statycznych i get works dla mnie

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

Gdzie indziej używane

var conf = ConstantThingy.NO_REENTER__INIT;
if(conf.init)...
 -1
Author: TroyWorks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-12 02:57:49