it-swarm.com.ru

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

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

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

461
wintercounter

2018 обновление:

В настоящее время есть предложение этапа 3 - я с нетерпением жду, чтобы этот ответ устарел через несколько месяцев.

Тем временем любой, кто использует TypeScript или babel, может использовать синтаксис:

varName = value

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

Обновление: Chrome 74 теперь поставляется с работающим синтаксисом.


Примечания в вики ES для предложения в ES6 ( максимально минимальные классы ) примечание:

Не существует (намеренно) прямого декларативного способа определения свойств класса данных прототипа (кроме методов) или свойства экземпляра

Свойства класса и свойства данных прототипа должны быть созданы вне объявления.

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

Это означает, что то, что вы просили, было рассмотрено и явно отклонено.

но почему?

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

Помните, определение класса определяет методы prototype - определение переменных в прототипе обычно не то, что вы делаете. Вы можете, конечно, использовать:

constructor(){
    this.foo = bar
}

В конструкторе, как вы предложили. Также см. краткое изложение консенсуса .

ES7 и выше

В настоящее время разрабатывается новое предложение для ES7, которое позволяет создавать более краткие переменные экземпляра с помощью объявлений и выражений классов - https://esdiscuss.org/topic/es7-property-initializers

484
Benjamin Gruenbaum

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

120
lyschoening

В вашем примере:

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'];}, вывод предупреждения представляет собой строку, false . В таком случае оператор 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
24
Oleg Mazko

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'
23
Kosmetika

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

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

 "use strict"; 
 
 class MyClass 
 {
 // только объявляют ваши свойства и затем вызывают this.ClassName (); отсюда 
 constructor () {
 this.prop1 = 'blah 1'; 
 this.prop2 = 'blah 2'; 
 this.prop3 = 'blah 3 '; 
 This.MyClass (); 
} 
 
 // все виды других "конструкторов", больше не смешанных с объявлениями 
 MyClass () {
 DoWhwhat (); 
} 
} 

Оба будут вызываться при создании нового экземпляра.

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

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


NOTE: Я очень целенаправленно не использую типичные идиоматические идеи "инициализации" (например, метод init() или initialize()), потому что они часто используются по-разному. Существует некоторая предполагаемая разница между идеей конструирования и инициализации. Работая с конструкторами, люди знают, что они вызываются автоматически как часть реализации. Увидев метод init, многие люди без малейшего взгляда поймут, что им нужно что-то делать в форме var mc = MyClass(); mc.init();, потому что именно так вы обычно инициализируете. Я не пытаюсь добавить процесс инициализации для пользователя класса, я пытаюсь добавить к процесс конструирования самого класса.

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


Для тех, кто не хочет следовать этому шаблону, может работать и обратное. Передайте объявления в другую функцию в начале. Может быть, назовите его "свойства" или "publicProperties" или что-то. Затем поместите остальные вещи в обычный конструктор.

 "использовать строгое"; 
 
 class MyClass 
 {
 properties () {
 this.prop1 = 'blah 1' ; 
 this.prop2 = 'blah 2'; 
 this.prop3 = 'blah 3'; 
} 
 
 constructor () {
 this.properties (); 
 doWhwhat (); 
} 
} 

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

17
Jimbo Jonny

Что насчет олдскульного пути?

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

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

14
zarkone

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

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

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

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

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

13
BonsaiOak

Длинная тема, не уверен, что она уже указана в качестве опции ...
Простая альтернатива для только для констант, определяла бы 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);
    }
}
7
Hertzel Guinness

Вы можете имитировать поведение классов 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

5
Ruslan

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);
4
Willem van der Veen

Я решил эту проблему, что является еще одним вариантом (если у вас есть 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, не стоит (почти наверняка незначительного) выигрыша в производительности.

0
Steve Childs

Ну, вы можете объявить переменные внутри конструктора.

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

var foo = new Foo()

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