Альтернативные переменные класса ES6

493

В настоящее время в 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также о какой-то интеграции.)

Какие у вас есть идеи, чтобы справиться с этой ситуацией?

wintercounter
источник
1
ваша главная проблема в том, что у вас будет повторение this.member = memberв вашем конструкторе с 20-30 параметрами?
Θεόφιλος Μουρατίδης
1
Разве вы не можете просто использовать public variable2 = trueв классе? Это определило бы это по прототипу.
14
@ Θεόφιλος Μουρατίδης: Да, а также я хочу использовать мой конструктор для процедур инициализации, а не для объявлений переменных.
счетчик зимнего сезона
@derylius: Это главная проблема, у нее нет такой функции. Даже публичное / частное использование еще не решено в проекте ES6. Проверь
пробную
Согласно последним, у него есть эта функция: wiki.ecmascript.org/doku.php?id=harmony:classes

Ответы:

515

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работает только для методов.
Берги
638
Возможно, хорошие люди в 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

Если вы также хотите иметь доступ к переменной вашего класса из экземпляра, вам придется дополнительно определить метод получения:

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) ищутся оттуда. Мы можем думать об этом как о генерации свойств объекта и класса.

lyschoening
источник
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'
Kosmetika
источник
1
Вы можете сделать бар с собственным переменным классом, притворяясь его с «#» , как это: #bar = 2;
Лонни Бест
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 Я думал о том же. Не обязательно вина ОП, хотя; это действительно язык, в котором отсутствуют надлежащие механизмы для представления данных таким образом, чтобы они отражали их отношения в реальном мире.
user1974458
17

Поскольку ваша проблема в основном стилистическая (не нужно заполнять конструктор кучей объявлений), ее можно решить и стилистически.

На мой взгляд, во многих языках на основе классов конструктор является функцией, названной в честь самого имени класса. Стилистически мы могли бы использовать это для создания класса ES6, который стилистически все еще имеет смысл, но не группирует типичные действия, выполняемые в конструкторе, со всеми объявлениями свойств, которые мы делаем. Мы просто используем фактический конструктор JS в качестве «области объявления», а затем создаем класс с именем function, который в противном случае мы рассматриваем как область «другого конструктора», вызывая его в конце истинного конструктора.

«использовать строгое»;

класс MyClass
{
    // объявляем только ваши свойства и затем вызываем this.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';
    }

    constructor () {
        this.properties ();
        делай что угодно();
    }
}

Обратите внимание, что этот второй метод может выглядеть чище, но он также имеет внутреннюю проблему, которая propertiesпереопределяется, поскольку один класс, использующий этот метод, расширяет другой. Вы должны были бы дать больше уникальных имен, propertiesчтобы избежать этого. Мой первый метод не имеет этой проблемы, потому что его фиктивная половина конструктора имеет уникальное имя в честь класса.

Джимбо Джонни
источник
1
Пожалуйста, не используйте метод-прототип, названный в честь самого класса. Это не идиоматично в JS, не пытайтесь сделать его похожим на другой язык. Если вы действительно хотите использовать этот подход, каноническое имя метода init.
Берги
1
@Bergi - initэто шаблон используется часто, и нередко приводит к тому , чтобы быть вызваны извне класса , когда внешний пользователь хочет инициализировать, то есть var b = new Thing(); b.init();. Это 100% стилистический выбор, который я предпочел бы сообщить, что это 2-я функция, которая автоматически вызывается с использованием шаблонов, найденных на других языках. Гораздо менее вероятно, что кто-то посмотрит на это и предположит, что ему нужно вызвать MyClassметод извне, более вероятно, что он поймет, что намерение - это 2-й метод, действующий в процессе разработки (т.е. вызываемый сам по себе при создании экземпляра).
Джимбо Джонни
Хм, я могу понять это, посмотрев на конструктор, но не на имя метода 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);

В конструкторе вы упоминаете только те переменные, которые должны быть вычислены. Мне нравится наследование прототипов для этой функции - она ​​может помочь сэкономить много памяти (в случае, если есть много никогда не назначенных переменных).

zarkone
источник
4
Это не экономит память, а просто делает производительность намного хуже, чем если бы вы объявили их в конструкторе. codereview.stackexchange.com/a/28360/9258
Esailija
2
Кроме того, это побеждает цель использования классов ES6 в первую очередь. В нынешнем виде вряд ли возможно использовать классы ES6, как настоящие классы ООП, что несколько разочаровывает ...
Kokodoko
4
@Kokodoko настоящий упс - ты имеешь в виду, например, в Java? Я согласен, я заметил, что многие люди злятся на JS, потому что они пытаются использовать его как Java, как они привыкли, потому что синтаксис JS выглядит аналогично, и они предполагают, что он работает так же ... Но из изнутри, он довольно сильно отличается, как мы знаем.
Zarkone
2
Это не только о синтаксисе языка, хотя. Философия ООП вполне пригодна для программирования в целом - особенно при создании приложений и игр. JS традиционно используется для создания веб-страниц, что совершенно иное. Каким-то образом эти два подхода должны объединиться, поскольку в Интернете все больше внимания уделяется приложениям.
Кокодоко
1
@Kokodoko Просто потому , что это разные ООП не означает , что это не ООП. Прототипы - это 100% -ный подход ООП; никто не собирается называть Self "не-ООП", потому что он использует прототипы. Несоответствие другой парадигме ООП означает только это: это отличается.
Дейв Ньютон,
13

Как сказал Бенджамин в своем ответе, TC39 явно решил не включать эту функцию по крайней мере для ES2015. Тем не менее, похоже, что они добавят его в ES2016.

Синтаксис еще не определен, но есть предварительное предложение для ES2016, которое позволит вам объявить статические свойства для класса.

Благодаря магии Вавилон, вы можете использовать это сегодня. Включите преобразование свойств класса в соответствии с этими инструкциями, и все готово. Вот пример синтаксиса:

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

Это предложение находится в очень раннем состоянии, поэтому будьте готовы изменить свой синтаксис со временем.

BonsaiOak
источник
Да, это не ES6. Если вы не знаете, что это такое, вы не должны его использовать.
Берги
10

Длинная тема, не уверен, что она уже указана в качестве опции ...
Простая альтернатива только для констант - определение константы вне класса. Это будет доступно только из самого модуля, если не сопровождается геттером.
Этот путь 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);
    }
}
Герцель Гиннесс
источник
Что произойдет, если вы а) используете varвместо const2) несколько экземпляров этого класса? Затем внешние переменные будут меняться с каждым экземпляром класса
Ник Каррауэй
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(),
        });

    }
}

Вы по-прежнему получаете «толстый» конструктор, но, по крайней мере, все это в одном классе и назначается в одном попадании.

РЕДАКТИРОВАТЬ # 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

Тем не менее, вы не можете объявить какие-либо классы, как в других языках программирования. Но вы можете создать столько переменных класса. Но проблема заключается в объеме объекта класса. Итак, по мне, лучший способ ООП программирования в ES6 Javascript: -

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)...
TroyWorks
источник
7
Я бы порекомендовал singleton_instanceв качестве имени свойства, чтобы все понимали, что вы здесь делаете.
Берги