Объявление статических констант в классах ES6?

312

Я хочу реализовать константы в a class, потому что именно здесь имеет смысл найти их в коде.

До сих пор я реализовал следующий обход статическими методами:

class MyClass {
    static constant1() { return 33; }
    static constant2() { return 2; }
    // ...
}

Я знаю, что есть возможность возиться с прототипами, но многие рекомендуют против этого.

Есть ли лучший способ реализовать константы в классах ES6?

Жером Верстринг
источник
7
Лично я просто использую VARNAMES в верхнем регистре и говорю себе, чтобы не трогать их;)
дваждыjr
3
@twicejr Я думаю, что это не то же самое, для статических переменных можно получить доступ без предварительного создания объекта этого класса?
Лукас Морган

Ответы:

386

Вот несколько вещей, которые вы могли бы сделать:

Экспорт constиз модуля . В зависимости от вашего варианта использования вы можете просто:

export const constant1 = 33;

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

const constant1 = 33,
      constant2 = 2;
class Example {

  static get constant1() {
    return constant1;
  }

  static get constant2() {
    return constant2;
  }
}

Таким образом, вам не понадобится скобка:

const one = Example.constant1;

Пример Babel REPL

Тогда, как вы говорите, поскольку a classявляется просто синтаксическим сахаром для функции, вы можете просто добавить недоступное для записи свойство следующим образом:

class Example {
}
Object.defineProperty(Example, 'constant1', {
    value: 33,
    writable : false,
    enumerable : true,
    configurable : false
});
Example.constant1; // 33
Example.constant1 = 15; // TypeError

Было бы хорошо, если бы мы могли сделать что-то вроде:

class Example {
    static const constant1 = 33;
}

Но, к сожалению, этот синтаксис свойства класса есть только в предложении ES7, и даже тогда он не позволит добавить constего в свойство.

CodingIntrigue
источник
есть ли подтверждение того, что статические свойства вычисляются один раз для подобных вещей, или безопаснее использовать IIFE и добавить свойство вручную в IIFE, чтобы избежать повторного построения возвращаемых значений. Я обеспокоен тем, что если результат получателя действительно тяжелый, как, например, JSObject из 100000 записей, тогда бедному получателю придется его создавать каждый раз, когда вызывается получатель. Его легко проверить с помощью performance.now/date diff, но он может быть реализован по-другому, его, безусловно, проще реализовать в качестве получателя в качестве буквальной оценки, а не при принятии сложных решений, независимо от того, постоянна она или нет.
Дмитрий
3
в то время как вышеупомянутое ловко добавляет свойство константы к классу, фактическое значение константы находится «вне» определения класса «{}», что действительно нарушает одно из определений инкапсуляции. Я предполагаю, что достаточно просто определить постоянное свойство "внутри" класса, и в этом случае нет необходимости в get.
NoChance
1
@NoChance Хорошие очки. Это было просто иллюстративно. Нет причины, по которой метод getter не может полностью инкапсулировать значение, если это необходимо.
CodingIntrigue
С нетерпением жду возможности использовать предложение ES7, потому что оно кажется мне более естественным и эквивалентным большинству языков ОО.
Сангимед
Что это я хочу объявить константой переменной экземпляра? Могу ли я сделать что-то вродеthis.defineProperty(this, 'constant1', {...})
Франческо Бой
33
class Whatever {
    static get MyConst() { return 10; }
}

let a = Whatever.MyConst;

Кажется, работает на меня.

Бенни Джобиган
источник
это доступно внутри класса в обычном методе?
PirateApp
3
@PirateApp вы можете получить к нему доступ в любом месте как статический метод, даже внутри экземпляра класса. Однако, поскольку он статичен, вы не можете использовать его this.MyConstвнутри Whateverэкземпляра, вы всегда должны писать его так: Whatever.MyConst
TheDarkIn1978
23

Я использую babelи следующий синтаксис работает для меня:

class MyClass {
    static constant1 = 33;
    static constant2 = {
       case1: 1,
       case2: 2,
    };
    // ...
}

MyClass.constant1 === 33
MyClass.constant2.case1 === 1

Пожалуйста, учтите, что вам нужен пресет "stage-0".
Чтобы установить это:

npm install --save-dev babel-preset-stage-0

// in .babelrc
{
    "presets": ["stage-0"]
}

Обновить:

в настоящее время используют stage-3

borracciaBlu
источник
21
Проблема в том, что константа переназначается. Оп не хочет этого
CodingIntrigue
3
К вашему сведению, это теперь в stage-2
Babel
3
это не константы
Дейв Л.
1
@CodingIntrigue Будет ли вызов Object.freeze()в классе исправить это?
Сурьма
1
@ Сурьма Я не проверял это, но я бы так подумал. Проблема в том, что это применимо ко всем свойствам класса. Нестатично тоже.
CodingIntrigue
14

В этом документе говорится:

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

Это означает, что это намеренно так.

Может быть, вы можете определить переменную в конструкторе?

constructor(){
    this.key = value
}
DevAlien
источник
2
Да, это может работать. Кроме того, я хочу отметить, что конструктор вызывается при создании экземпляра, и для каждого экземпляра this.key не будет одинаковым. Статический метод и свойства позволяют нам использовать их непосредственно из класса, не создавая экземпляр. Есть хорошие и слабые стороны статических методов / свойств.
Кирилл Гусятин
1
Константы должны быть неизменными. Присвоение свойств объекту во время строительства даст свойства, которые можно изменить.
Филрай
11

Также можно использовать Object.freezeобъект класса (es6) / function constructor (es5), чтобы сделать его неизменным:

class MyConstants {}
MyConstants.staticValue = 3;
MyConstants.staticMethod = function() {
  return 4;
}
Object.freeze(MyConstants);
// after the freeze, any attempts of altering the MyConstants class will have no result
// (either trying to alter, add or delete a property)
MyConstants.staticValue === 3; // true
MyConstants.staticValue = 55; // will have no effect
MyConstants.staticValue === 3; // true

MyConstants.otherStaticValue = "other" // will have no effect
MyConstants.otherStaticValue === undefined // true

delete MyConstants.staticMethod // false
typeof(MyConstants.staticMethod) === "function" // true

Попытка изменить класс даст вам мягкий сбой (не будет выдавать никаких ошибок, это просто не будет иметь никакого эффекта).

rodrigo.botti
источник
3
Этот программный сбой довольно страшен для тех из нас, кто работает с другими языками - просто приспосабливается к идее, что инструменты не очень помогают нам в поиске ошибок, теперь даже время выполнения не поможет. (В противном случае мне нравится ваше решение.)
Том
Я люблю Object.freeze()для обеспечения неизменности, и в последнее время очень много использовал его. Только не забудьте применить это рекурсивно!
jeffwtribble
6

Может быть, просто положить все свои константы в замороженный объект?

class MyClass {

    constructor() {
        this.constants = Object.freeze({
            constant1: 33,
            constant2: 2,
        });
    }

    static get constant1() {
        return this.constants.constant1;
    }

    doThisAndThat() {
        //...
        let value = this.constants.constant2;
        //...
    }
}
Ariel
источник
Статическая функция не может использовать переменную this.
PokerFace
4

Как сказал https://stackoverflow.com/users/2784136/rodrigo-botti , я думаю, что вы ищете Object.freeze(). Вот пример класса с неизменной статикой:

class User {
  constructor(username, age) {
    if (age < User.minimumAge) {
      throw new Error('You are too young to be here!');
    }
    this.username = username;
    this.age = age;
    this.state = 'active';
  }
}

User.minimumAge = 16;
User.validStates = ['active', 'inactive', 'archived'];

deepFreeze(User);

function deepFreeze(value) {
  if (typeof value === 'object' && value !== null) {
    Object.freeze(value);
    Object.getOwnPropertyNames(value).forEach(property => {
      deepFreeze(value[property]);
    });
  }
  return value;
}
jeffwtribble
источник
1

Вот еще один способ, которым вы можете сделать

/*
one more way of declaring constants in a class,
Note - the constants have to be declared after the class is defined
*/
class Auto{
   //other methods
}
Auto.CONSTANT1 = "const1";
Auto.CONSTANT2 = "const2";

console.log(Auto.CONSTANT1)
console.log(Auto.CONSTANT2);

Обратите внимание - порядок важен, вы не можете иметь константы выше

Использование console.log (Auto.CONSTANT1);

user3871424
источник
5
Они не являются неизменными, хотя
Джон Хардинг
1

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

const withConsts = (map, BaseClass = Object) => {
  class ConstClass extends BaseClass { }
  Object.keys(map).forEach(key => {
    Object.defineProperty(ConstClass, key, {
      value: map[key],
      writable : false,
      enumerable : true,
      configurable : false
    });
  });
  return ConstClass;
};

class MyClass extends withConsts({ MY_CONST: 'this is defined' }) {
  foo() {
    console.log(MyClass.MY_CONST);
  }
}
TbWill4321
источник
1

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

class Foo {
    static BAR = "bat"; //public static read-only
}

Object.freeze(Foo); 

/*
Uncaught TypeError: Cannot assign to read only property 'BAR' of function 'class Foo {
    static BAR = "bat"; //public static read-only
}'
*/
Foo.BAR = "wut";
Фрейзер
источник
0

Если вам удобно смешивать и сопоставлять синтаксис функции и класса, вы можете объявить константы после класса (константы «отменены»). Обратите внимание, что Visual Studio Code будет пытаться автоматически отформатировать смешанный синтаксис (хотя он работает).

class MyClass {
    // ...

}
MyClass.prototype.consts = { 
    constant1:  33,
    constant2: 32
};
mc = new MyClass();
console.log(mc.consts.constant2);    

Cam Cairns
источник
0

Я сделал это.

class Circle
{
    constuctor(radius)
    {
        this.radius = radius;
    }
    static get PI()
    {
        return 3.14159;
    }
}

Значение PI защищено от изменения, поскольку оно является значением, возвращаемым функцией. Вы можете получить к нему доступ через Circle.PI. Любая попытка присвоить ему просто отбрасывается на пол способом, аналогичным попытке присвоить строковый символ через [].

ncmathsadist
источник
0

Вы можете определить это так:

class Foo {
  static MyConst = 200;

  myFunc() {
    const doubleConst = Foo.MyConst * 2;
  }
}
zmechanic
источник
0

Вы могли бы использовать import * asсинтаксис. Хотя это и не класс, они являются реальными constпеременными.

Constants.js

export const factor = 3;
export const pi = 3.141592;

index.js

import * as Constants from 'Constants.js'
console.log( Constants.factor );
Винсент
источник