Змінні альтернативи класу ES6


491

В даний час в ES5 багато хто з нас використовують таку схему в рамках, щоб створити класи та змінні класу, що є зручним:

// ES 5
FrameWork.Class({

    variable: 'string',
    variable2: true,

    init: function(){

    },

    addItem: function(){

    }

});

У ES6 ви можете створювати класи на самому собі, але немає варіантів мати змінні класу:

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

На жаль, вищезгадане не спрацює, оскільки лише класи можуть містити методи.

Я розумію, що можу this.myVar = trueв constructor… але я не хочу «мотлох» свого конструктора, особливо коли у мене є 20-30 + парам для більшого класу.

Я думав про багато способів вирішити цю проблему, але ще не знайшов жодного хорошого. (Наприклад: створити ClassConfigобробник і передати parameterоб’єкт, який оголошується окремо від класу. Тоді обробник приєднається до класу. Я думав про те, WeakMapsщоб якось інтегруватись.)

Які ідеї ви мали б вирішити у цій ситуації?


1
Ваша основна проблема полягає в тому, що у вас буде повторення this.member = memberу вашому конструкторі з 20-30 параметрами?
Θεόφιλος Μουρατίδης

1
Ви не можете просто використовувати public variable2 = trueпід класом? Це визначило б це на прототипі.

14
@ Θεόφιλος Μουρατίδης: Так, а також я хочу використовувати свій конструктор для процедур ініціалізації, а не для змінних оголошень.
Wintercounter

@derylius: Це головна проблема, вона не має такої функції. Навіть державне / приватне використання ще не визначено в проекті ES6. Надайте йому тестовий спин: es6fiddle.net
wintercounter

Відповідно до останніх, вона має цю функцію: wiki.ecmascript.org/doku.php?id=harmony:classes

Відповіді:


514

Оновлення 2018 року:

Зараз є пропозиція на етапі 3 - я з нетерпінням чекаю зробити цю відповідь застарілою через кілька місяців.

Тим часом кожен, хто використовує TypeScript або babel, може використовувати синтаксис:

varName = value

Всередині декларації / виразу класу, і він визначатиме змінну. Сподіваюся, через кілька місяців / тижнів я зможу опублікувати оновлення.

Оновлення: Chrome 74 тепер постачається з цим синтаксисом.


Примітки у вікі ES для пропозиції в ES6 ( максимально мінімальні класи ) Примітка:

Не існує (навмисно) прямого декларативного способу визначення властивостей даних прототипу (крім методів) властивостей класу, або властивості екземпляра

Властивості класу та властивості даних прототипу потрібно створити поза декларацією.

Властивості, визначені у визначенні класу, присвоюються тими ж атрибутами, як якщо б вони з'явилися в літералі об'єкта.

Це означає, що те, про що ви просите, було розглянуто і було явно проти.

але чому?

Хороше питання. Хороші люди TC39 хочуть, щоб декларації класу заявляли та визначали можливості класу. Не його члени. Декларація класу ES6 визначає контракт для свого користувача.

Пам’ятайте, визначення класу визначає методи прототипу - визначення змінних на прототипі, як правило, не те, що ви робите. Можна, звичайно, використовувати:

constructor(){
    this.foo = bar
}

У конструкторі, як ви запропонували. Також дивіться підсумок консенсусу .

ES7 і далі

Робота над новою пропозицією для ES7, яка дозволяє більш стислих змінних примірників через декларації класів та вирази - https://esdiscuss.org/topic/es7-property-initializers


4
@wintercounter, що важливо взяти з нього, це те, що дозволяючи визначати властивості, визначали б їх у прототипі як методи, а не на кожному екземплярі. Максимально мінімальні класи до цих пір є основою прототипічного успадкування. Що ви дійсно хочете зробити у вашому випадку - це структура спільного використання та призначення членів для кожного примірника. Це просто не те, на що спрямовані класи в ES6 - обмін функціоналом. Так, так, для спільної структури вам доведеться дотримуватися старого синтаксису. До ES7 принаймні :)
Бенджамін Грюнбаум

5
Ви можете згадати staticвластивості
Бергі,

8
Ой, мозок пердеть. Я забув, що staticпрацює лише для методів.
Бергі

637
Можливо, хороші люди в TC39 повинні називати це поняття чимось іншим, ніж "клас", якщо вони не хочуть, щоб він поводився так, як решта програмного світу очікує від чогось, що називається "клас".
Олексій

7
Дивіться також пропозицію "Поля класу та статичні властивості" (вже впроваджена в Babel : github.com/jeffmo/es-class-fields-and-static-properties
Метт Браун

127

Просто для додання відповіді Бенджаміна - змінні класу можливі, але ви не використовуєте їх prototypeдля встановлення.

Для справжньої змінної класу ви хочете зробити щось на кшталт наступного:

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

Із класового методу до цієї змінної можна отримати доступ до this.constructor.foo(або MyClass.foo).

Ці властивості класу зазвичай не можуть бути доступні для екземпляра класу. тобто MyClass.fooдає, 'bar'але new MyClass().fooєundefined

Якщо ви також хочете мати доступ до змінної вашого класу з екземпляра, вам доведеться додатково визначити getter:

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

MyClass.foo = 'bar';

Я протестував це лише з Traceur, але я вважаю, що він буде працювати однаково в стандартній реалізації.

У JavaScript насправді немає класів . Навіть з ES6 ми дивимось на мову, що базується на об'єктах чи прототипі, а не на основі класу. У будь-якому function X () {}, X.prototype.constructorщо посилається назад X. Коли newоператор використовується X, новий об’єкт створюється у спадок X.prototype. Будь-які невизначені властивості цього нового об'єкта (включаючи constructor) шукаються звідти. Ми можемо вважати це генеруванням властивостей об'єктів і класів.


29

Babel підтримує змінні класу в ESNext, перевірте цей приклад :

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'

1
Ви можете зробити бар з власним змінним класом, прикидаючись його з «#» , як це: #bar = 2;
Lonnie Best

26

У вашому прикладі:

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

Через те, що MY_CONST є примітивним https://developer.mozilla.org/en-US/docs/Glossary/Primitive, ми можемо просто зробити:

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

Але якщо MY_CONSTтакий тип посилань, як static get MY_CONST() {return ['string'];}вихідний сигнал попередження, є рядковим, хибним . У такому випадку deleteоператор може виконати трюк:

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

І, нарешті, для змінної класу немає 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

5
Будь ласка, уникайте deleteоператора, якщо він не один з причин ефективності. Те , що ви на справді хочете тут Object.defineProperty.
Бергі

@shinzou Я думав те саме. Не обов’язково вина ОП; це справді мова, якій не вистачає належних механізмів подання даних таким чином, що відображає її реальні стосунки у світі.
користувач1974458

17

Оскільки ваше питання здебільшого стилістичне (не бажаючи заповнювати конструктор купою декларацій), воно також може бути вирішене стилістично.

Те, як я бачу це, у багатьох мовах на основі класу конструктор є функцією, названою назвою самого імені класу. Стилістично ми могли б використати це для створення класу ES6, який стилістично все ще має сенс, але не групує типові дії, що відбуваються в конструкторі, з усіма деклараціями властивостей, які ми робимо. Ми просто використовуємо фактичний конструктор JS як "область декларування", а потім робимо функцію класу з назвою, яку ми інакше трактуємо як область "інші елементи конструктора", називаючи її в кінці справжнього конструктора.

"використовувати суворо";

клас MyClass
{
    // декларуйте лише свої властивості, а потім викликайте це.ClassName (); звідси
    конструктор () {
        this.prop1 = 'бла 1';
        this.prop2 = 'бла 2 ";
        this.prop3 = 'бла 3';
        this.MyClass ();
    }

    // всілякі інші "конструкторські" речі, більше не змішані з деклараціями
    Мій клас() {
        робити що-небудь ();
    }
}

Обидва будуть називатися, як будується новий екземпляр.

Сорта любить мати 2 конструктори, де ви відокремлюєте декларації та інші дії конструктора, які ви хочете вжити, і стилістично це не дуже важко зрозуміти, що це теж відбувається.

Я вважаю, що це приємний стиль для використання, коли ми маємо багато декларацій та / або безліч дій, які потребують здійснення при створенні примірників, і хочуть, щоб ці ідеї не відрізнялися одна від одної.


ПРИМІТКА : Я цілеспрямовано не використовую типові ідіоматичні ідеї "ініціалізації" (як-от init()або initialize()метод), тому що вони часто використовуються по-різному. Існує свого роду припущена різниця між ідеєю побудови та ініціалізації. Працюючи з конструкторами, люди знають, що вони автоматично викликаються як частина екземплярів. Бачачи initметод, багато людей збираються без другого погляду припустити, що їм потрібно щось робити у формі var mc = MyClass(); mc.init();, тому що ти зазвичай ініціалізуєш. Я не намагаюся додати процес ініціалізації для користувача класу, я намагаюся додати до процесу побудови самого класу.

Хоча деякі люди можуть зробити миттєвий подвійний процес, це насправді суть справи: він повідомляє їм, що намір є частиною побудови, навіть якщо це змушує їх зробити подвійний підйом і піти "це не так як працюють конструктори ES6 "і по-другому дивлячись на фактичний конструктор, щоб перейти" о, вони називають це внизу, я бачу ", це набагато краще, ніж НЕ повідомляти про цей намір (або неправильно передавати його) і, ймовірно, отримувати багато люди неправильно використовують його, намагаючись ініціалізувати його ззовні та сміття. Це дуже навмисно для тієї схеми, яку я пропоную.


Для тих, хто не хоче йти за цією схемою, може бути і навпаки. Обробляйте декларації на іншій функції на початку. Можливо, назвіть це "властивості" чи "publicProperties" чи щось. Потім покладіть решту речі в звичайний конструктор.

"використовувати суворо";

клас MyClass
{
    властивості () {
        this.prop1 = 'бла 1';
        this.prop2 = 'бла 2 ";
        this.prop3 = 'бла 3';
    }

    конструктор () {
        this.properties ();
        робити що-небудь ();
    }
}

Зауважте, що цей другий метод може виглядати більш чистим, але він також має властиву проблему, коли propertiesйого переосмислення, оскільки один клас за допомогою цього методу розширює інший. Щоб propertiesуникнути цього, вам доведеться дати більше унікальних імен . У мого першого методу немає цієї проблеми, оскільки його підроблена половина конструктора однозначно названа на честь класу.


1
Будь ласка, не використовуйте метод прототипу, названий на честь самого класу. Це неідіоматично в JS, не намагайтеся зробити так, щоб це було схоже на іншу мову. Якщо ви дійсно хочете використовувати цей підхід, канонічна назва методу - це init.
Бергі

1
@Bergi - initце шаблон, який часто використовується, і часто призначений для виклику поза класом, коли зовнішній користувач хоче ініціалізувати, тобто var b = new Thing(); b.init();. Це стовідсотковий стилістичний вибір, який я вважаю за краще повідомити, що це 2-а функція, яка автоматично викликається, скориставшись шаблонами інших мов. Набагато менше ймовірності, що хтось погляне на це і припустить, що їм потрібно викликати MyClassметод ззовні, швидше за все, що вони зрозуміють, що намір є другим методом, що діє в будівництві (тобто називається сам по інстанції).
Джимбо Джоні

Гм, я можу зрозуміти, що дивлячись на конструктор, але не з назви методу MyClass.
Бергі

1
@Bergi - Візьміть шаблон з іншої мови та застосуйте його до JS таким чином, що технічно це не відбувається, але все ще працює, не зовсім без прецеденту. Ви не можете сказати мені, що ніхто не помічав, що $myvarстандартний спосіб посилань на змінні, призначені для розміщення об’єктів jQuery, не є зручним аналогічно шаблону наявності $ на початку змінних, до яких використовується стільки PHP-програмістів. Якраз той невеликий сенс, що "так, це не та сама точна річ ... але дивіться ... це все-таки змінна, тому що це спосіб, що змінні виконуються в деяких мовах!" допомагає.
Джимбо Джоні

1
Я вважаю за краще ваш останній варіант з properties()функцією. Однак, при розширенні класів все стає дещо складніше, тому я б запропонував, якщо ви використовуєте properties()функцію у всіх своїх класах, щоб оголосити вам властивості класу, то ви встановите префікс ім'я методу з назвою класу (IE: MyClassProperties()щоб уникнути випадкового перезазначення функціональні дзвінки в підкласах. Також майте на увазі, що будь-які дзвінки super()повинні бути оголошені першими у конструкторі класів.
TheDarkIn1978,

14

А як щодо старого шкільного способу?

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

У конструкторі ви згадуєте лише ті варіанти, які потрібно обчислити. Мені подобається спадкування прототипу за цією функцією - це може допомогти зберегти багато пам’яті (на випадок, якщо існує багато ніколи не призначених варіантів).


4
Це не економить пам'ять, і це просто робить продуктивність набагато гіршою, ніж якби ви оголосили їх у конструкторі. codereview.stackexchange.com/a/28360/9258
Esailija

2
Також це перешкоджає насамперед використанню класів ES6. Як відомо, навряд чи можливо використовувати класи ES6, як реальні класи oop, що дещо розчаровує ...
Кокодоко

4
@Kokodoko real oop - ти маєш на увазі, наприклад, на Java? Я згоден, я помітив, що багато людей розлючені на JS, тому що вони намагаються використовувати його як Java, як вони звикають, тому що синтаксис JS виглядає схоже, і вони припускають, що він працює так само ... Але з зсередини, він досить різний, як ми знаємо.
зарконе

2
Хоча мова не лише про синтаксис мови. Філософія OOP досить добре піддається програмуванню в цілому - особливо при створенні додатків та ігор. JS традиційно впроваджується у створення веб-сторінок, що зовсім інше. Ці два підходи потрібно якось поєднувати, оскільки в Інтернеті все більше ставиться і до додатків.
Кокодоко

1
@Kokodoko Тільки тому, що це інший OOP, це не означає, що це не OOP. Прототипи являють собою 100% недійсний підхід OOP; ніхто не збирається називати самості "не-OOP", оскільки він використовує прототипи. Не узгодження з іншою парадигмою OOP означає лише це: вона різна.
Дейв Ньютон

13

Як зазначив Бенджамін у своїй відповіді, TC39 прямо вирішив не включати цю функцію принаймні для ES2015. Однак, мабуть, існує консенсус, що вони додадуть його у ES2016.

Синтаксис ще не визначено, але є попередня пропозиція для ES2016, яка дозволить вам оголошувати статичні властивості для класу.

Завдяки магії вавилони, ви можете використовувати це сьогодні. Увімкніть перетворення властивостей класу відповідно до цих вказівок, і ви готові йти. Ось приклад синтаксису:

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

Ця пропозиція знаходиться в дуже ранньому стані, тому будьте готові налаштувати свій синтаксис з часом.


Так, це не ES6. Якщо ви не знаєте, що це таке, не слід його використовувати.
Бергі

10

[Довга нитка, не впевнений, чи її вже вказано як варіант ...].
Простий альтернативою лише для континентів - це визначення const поза класом. Це буде доступне лише у самому модулі, якщо його не матиме геттер.
Цей спосіб prototypeне засмічений, і ви отримаєте 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);
    }
}

Що станеться, якщо ви a) використовуєте varзамість const2) використовуєте кілька примірників цього класу? Тоді зовнішні змінні будуть змінені з кожним екземпляром класу
nick carraway

1
Точка провалу @nickcarraway, моя пропозиція означає лише const, а не те, що під назвою.
Герцель Гіннес

6

ES7 синтаксис члена класу:

ES7має рішення для "з'єднання" вашої функції конструктора. Ось приклад:

class Car {
  
  wheels = 4;
  weight = 100;

}

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

Наведений вище приклад виглядатиме наступним чином ES6:

class Car {

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

}

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

Будьте в курсі, використовуючи це, що цей синтаксис може підтримуватися не в усіх браузерах, і, можливо, доведеться перекласти попередню версію JS.

Бонус: фабрика об'єктів:

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


3
Ви виконали змінні екземпляра, а не змінні класу.
Тяд Кларк

5

Ви можете імітувати поведінку класів es6 ... і використовувати змінні класу :)

Подивися мамо ... ніяких занять!

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

Я поклав це на GitHub


0

Я вирішив це, що є ще одним варіантом (якщо у вас є jQuery) - це визначити поля в об’єкті старої школи, а потім розширити клас із цим об'єктом. Я також не хотів перецьвати конструктор із завданнями, це виявилося акуратним рішенням.

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

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

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

- Оновлення Після коментаря Бергі.

Немає версії JQuery:

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

    }
}

Ви все ще закінчуєтеся з "жирним" конструктором, але принаймні все це в одному класі та присвоєно одному хіту.

EDIT № 2: Зараз я пройшов повне коло і тепер призначаю значення конструктору, наприклад

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

Чому? Насправді просто, використовуючи вищезазначене плюс деякі коментарі JSdoc, PHPStorm зміг виконати завершення коду для властивостей. Призначення всіх варіантів одним хітом було приємно, але неможливість кодування завершити властивості, imo, не вартує (майже напевно мінусової) переваги від продуктивності.


2
Якщо ви можете зробити classсинтаксис, ви також можете зробити Object.assign. Не потрібно jQuery.
Бергі

Я не бачу сенсу робити MyClassFieldsконструктор - у нього немає ніяких методів. Чи можете ви пояснити, чому ви це зробили (замість, скажімо, простої заводської функції, яка повертає об'єкт буквально)?
Бергі

@ Бергі - Швидше кажучи, сила звички, а не все інше. Також приємний дзвінок про Object.assign - про це не знав!
Стів Чайлдс

0

Ну, ви можете оголосити змінні всередині конструктора.

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

var foo = new Foo()

foo.method()

1
Для використання цієї змінної вам потрібно буде створити примірник цього класу. Статичні функції не можуть отримати доступ до цієї змінної
Aditya

0

Проте ви не можете оголосити будь-які класи, як на інших мовах програмування. Але ви можете створити якомога більше змінних класу. Але проблема - це сфера об'єкта класу. Тож, на мою думку, найкращий спосіб програмування OOP в Javascript ES:

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

-1

Це трохи хакітський комбо статики і отримати роботи для мене

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

в інших місцях

var conf = ConstantThingy.NO_REENTER__INIT;
if(conf.init)...

7
Я б рекомендував singleton_instanceяк назву власності, щоб усі розуміли, що ви тут робите.
Бергі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.