Перераховує Javascript з ES6


136

Я відновлював старий проект Java в Javascript і зрозумів, що не існує хорошого способу зробити перерахунки в JS.

Найкраще, що я можу придумати, це:

const Colors = {
    RED: Symbol("red"),
    BLUE: Symbol("blue"),
    GREEN: Symbol("green")
};
Object.freeze(Colors);

constЗберігає Colorsвід бути змінені, і заморожування він запобігає мутує ключі і значення. Я використовую Symbols так, що Colors.REDне є рівним 0, або будь-що інше, крім самого себе.

Чи є проблема з цією рецептурою? Чи є кращий спосіб?


(Я знаю, що це питання трохи повторюється, але всі попередні питання Q / як це вже досить давно, і ES6 дає нам нові можливості.)


Редагувати:

Ще одне рішення, яке стосується проблеми серіалізації, але я вважаю, що все ще є проблеми:

const enumValue = (name) => Object.freeze({toString: () => name});

const Colors = Object.freeze({
    RED: enumValue("Colors.RED"),
    BLUE: enumValue("Colors.BLUE"),
    GREEN: enumValue("Colors.GREEN")
});

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


2
це був би ідеальний підхід у es6. Вам не доведеться заморожувати його
Нірус

2
@Nirus ви це робите, якщо ви не хочете, щоб його змінювали.
zerkms

2
Ви помітили цю відповідь ?
Бергі

3
Я можу подумати про одне питання: Не можу використати цю перерахунок JSON.stringify(). Неможливо серіалізувати / десеріалізувати Symbol.
le_m

1
@ErictheRed Я протягом багатьох років використовую постійні значення перерахунків рядків без зайвих клопотів, тому що використання потоку (або TypeScript) гарантує набагато більшу безпеку типу, ніж колись колись воляться про уникнення зіткнення
Енді,

Відповіді:


131

Чи є проблема з цією рецептурою?

Я не бачу жодної.

Чи є кращий спосіб?

Я би збив два твердження в одне:

const Colors = Object.freeze({
    RED:   Symbol("red"),
    BLUE:  Symbol("blue"),
    GREEN: Symbol("green")
});

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


3
Хіба тут немає проблем із цариною?

2
@torazaburo Ви маєте на увазі, коли код завантажується двічі, він буде генерувати різні символи, що не буде проблемою з рядками? Так, хороший момент, зробіть це відповіддю :-)
Бергі

2
@ErictheRed Ні, Symbol.for це НЕ має проблем крос-Realm, однак він має звичайну проблему зіткнення з воістину глобальним простором імен .
Бергі

1
@ErictheRed Це дійсно гарантує створення точно такого ж символу незалежно від того, коли і де (з якої сфери / рамки / вкладки / процесу) він називається
Бергі

1
@jamesemanon Можна отримати опис, якщо хочете , але я б використовував його в першу чергу лише для налагодження. Швидше мати власну функцію перетворення від перерахунку до рядків, як зазвичай (щось уздовж рядків enum => ({[Colors.RED]: "bright red", [Colors.BLUE]: "deep blue", [Colors.GREEN]: "grass green"}[enum])).
Бергі

18

Хоча використання Symbolяк значення enum добре працює для простих випадків використання, може бути зручно надати перелікам властивості. Це можна зробити за допомогою Objectзначення enum, що містить властивості.

Наприклад, ми можемо дати кожному Colorsім’я та шістнадцяткове значення:

/**
 * Enum for common colors.
 * @readonly
 * @enum {{name: string, hex: string}}
 */
const Colors = Object.freeze({
  RED:   { name: "red", hex: "#f00" },
  BLUE:  { name: "blue", hex: "#00f" },
  GREEN: { name: "green", hex: "#0f0" }
});

Включення властивостей у enum дозволяє уникнути необхідності писати switchзаяви (і, можливо, забувати нові випадки до операторів перемикання, коли enum розширено). Приклад також показує властивості та типи переліків, задокументовані анотацією JSDoc enum .

Рівність працює так, як очікувалося, з Colors.RED === Colors.REDбуттям trueі Colors.RED === Colors.BLUEбуттям false.


9

Як було сказано вище, ви також можете написати функцію makeEnum()помічника:

function makeEnum(arr){
    let obj = {};
    for (let val of arr){
        obj[val] = Symbol(val);
    }
    return Object.freeze(obj);
}

Використовуйте його так:

const Colors = makeEnum(["red","green","blue"]);
let startColor = Colors.red; 
console.log(startColor); // Symbol(red)

if(startColor == Colors.red){
    console.log("Do red things");
}else{
    console.log("Do non-red things");
}

2
Як одноразовий: const makeEnum = (...lst) => Object.freeze(Object.assign({}, ...lst.map(k => ({[k]: Symbol(k)})))); Потім використовуйте його як const colors = makeEnum("Red", "Green", "Blue")
Мануель Еберт

9

Це мій особистий підхід.

class ColorType {
    static get RED () {
        return "red";
    }

    static get GREEN () {
        return "green";
    }

    static get BLUE () {
        return "blue";
    }
}

// Use case.
const color = Color.create(ColorType.RED);

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

7

Перевірте, як це робить TypeScript . В основному вони роблять наступне:

const MAP = {};

MAP[MAP[1] = 'A'] = 1;
MAP[MAP[2] = 'B'] = 2;

MAP['A'] // 1
MAP[1] // A

Використовуйте символи, заморожуйте об'єкт, що завгодно.


Я не стежу за тим, чому це використовується MAP[MAP[1] = 'A'] = 1;замість MAP[1] = 'A'; MAP['A'] = 1;. Я завжди чув, що використовувати завдання як вираз - це поганий стиль. Крім того, яку вигоду ви отримуєте від дзеркальних завдань?
Ерік Червоний

1
Ось посилання на те, як відображається перерахування перерахунків до es5 у своїх документах. typescriptlang.org/docs/handbook/enums.html#reverse-mappings я зображення було б просто бути простіше і більш коротким , щоб скомпілювати його в одному рядку , наприклад MAP[MAP[1] = 'A'] = 1;.
givehug

Ага. Таким чином, схоже, що дзеркальне відображення просто полегшує перемикання між рядковими та числовими / символьними поданнями кожного значення та перевіряє, чи якась рядок чи число / символ xє дійсним значенням Enum, виконуючи це Enum[Enum[x]] === x. Це не вирішує жодних моїх оригінальних проблем, але може бути корисним і нічого не порушує.
Ерік Червоний

1
Майте на увазі, що TypeScript додає шар надійності, який втрачається, коли компілюється код TS. Якщо весь ваш додаток написано на TS, це чудово, але якщо ви хочете, щоб JS-код був надійним, заморожена карта символів звучить як безпечніша картина.
Доміно



1

Може це рішення? :)

function createEnum (array) {
  return Object.freeze(array
    .reduce((obj, item) => {
      if (typeof item === 'string') {
        obj[item.toUpperCase()] = Symbol(item)
      }
      return obj
    }, {}))
}

Приклад:

createEnum(['red', 'green', 'blue']);

> {RED: Symbol(red), GREEN: Symbol(green), BLUE: Symbol(blue)}

приклад використання буде дуже вдячний :-)
Абдеррахмане TAHRI JOUTI

0

Я віддаю перевагу підході @ tonethar, з деякими вдосконаленнями та копанням, щоб краще зрозуміти основи екосистеми ES6 / Node.js. З фоном на серверній стороні огорожі я віддаю перевагу підходу функціонального стилю навколо примітивів платформи, це мінімізує розрив коду, слизький нахил до долини управління державою тіні смерті через впровадження нових типів та збільшення читабельність - робить більш чіткими наміри рішення та алгоритму.

Розчин з TDD , ES6 , Node.js , Lodash , Jest , Babel , ESLint

// ./utils.js
import _ from 'lodash';

const enumOf = (...args) =>
  Object.freeze( Array.from( Object.assign(args) )
    .filter( (item) => _.isString(item))
    .map((item) => Object.freeze(Symbol.for(item))));

const sum = (a, b) => a + b;

export {enumOf, sum};
// ./utils.js

// ./kittens.js
import {enumOf} from "./utils";

const kittens = (()=> {
  const Kittens = enumOf(null, undefined, 'max', 'joe', 13, -13, 'tabby', new 
    Date(), 'tom');
  return () => Kittens;
})();

export default kittens();
// ./kittens.js 

// ./utils.test.js
import _ from 'lodash';
import kittens from './kittens';

test('enum works as expected', () => {
  kittens.forEach((kitten) => {
    // in a typed world, do your type checks...
    expect(_.isSymbol(kitten));

    // no extraction of the wrapped string here ...
    // toString is bound to the receiver's type
    expect(kitten.toString().startsWith('Symbol(')).not.toBe(false);
    expect(String(kitten).startsWith('Symbol(')).not.toBe(false);
    expect(_.isFunction(Object.valueOf(kitten))).not.toBe(false);

    const petGift = 0 === Math.random() % 2 ? kitten.description : 
      Symbol.keyFor(kitten);
    expect(petGift.startsWith('Symbol(')).not.toBe(true);
    console.log(`Unwrapped Christmas kitten pet gift '${petGift}', yeee :) 
    !!!`);
    expect(()=> {kitten.description = 'fff';}).toThrow();
  });
});
// ./utils.test.js

Array.from(Object.assign(args))не робить абсолютно нічого. Ви можете просто використовувати ...argsбезпосередньо.
Доміно

0

Ось мій підхід, включаючи деякі допоміжні методи

export default class Enum {

    constructor(name){
        this.name = name;
    }

    static get values(){
        return Object.values(this);
    }

    static forName(name){
        for(var enumValue of this.values){
            if(enumValue.name === name){
                return enumValue;
            }
        }
        throw new Error('Unknown value "' + name + '"');
    }

    toString(){
        return this.name;
    }
}

-

import Enum from './enum.js';

export default class ColumnType extends Enum {  

    constructor(name, clazz){
        super(name);        
        this.associatedClass = clazz;
    }
}

ColumnType.Integer = new ColumnType('Integer', Number);
ColumnType.Double = new ColumnType('Double', Number);
ColumnType.String = new ColumnType('String', String);

0

ви також можете використовувати пакет es6-enum ( https://www.npmjs.com/package/es6-enum ). Це дуже просто у використанні. Дивіться приклад нижче:

import Enum from "es6-enum";
const Colors = Enum("red", "blue", "green");
Colors.red; // Symbol(red)

10
який приклад нижче?
Олександр

якщо ви зробите приклад, люди будуть голосувати за вашу відповідь.
Артем Федотов

0

Ось моя реалізація переліку Java в JavaScript.

Я також включав одиничні тести.

const main = () => {
  mocha.setup('bdd')
  chai.should()

  describe('Test Color [From Array]', function() {
    let Color = new Enum('RED', 'BLUE', 'GREEN')
    
    it('Test: Color.values()', () => {
      Color.values().length.should.equal(3)
    })

    it('Test: Color.RED', () => {
      chai.assert.isNotNull(Color.RED)
    })

    it('Test: Color.BLUE', () => {
      chai.assert.isNotNull(Color.BLUE)
    })

    it('Test: Color.GREEN', () => {
      chai.assert.isNotNull(Color.GREEN)
    })

    it('Test: Color.YELLOW', () => {
      chai.assert.isUndefined(Color.YELLOW)
    })
  })

  describe('Test Color [From Object]', function() {
    let Color = new Enum({
      RED   : { hex: '#F00' },
      BLUE  : { hex: '#0F0' },
      GREEN : { hex: '#00F' }
    })

    it('Test: Color.values()', () => {
      Color.values().length.should.equal(3)
    })

    it('Test: Color.RED', () => {
      let red = Color.RED
      chai.assert.isNotNull(red)
      red.getHex().should.equal('#F00')
    })

    it('Test: Color.BLUE', () => {
      let blue = Color.BLUE
      chai.assert.isNotNull(blue)
      blue.getHex().should.equal('#0F0')
    })

    it('Test: Color.GREEN', () => {
      let green = Color.GREEN
      chai.assert.isNotNull(green)
      green.getHex().should.equal('#00F')
    })

    it('Test: Color.YELLOW', () => {
      let yellow = Color.YELLOW
      chai.assert.isUndefined(yellow)
    })
  })

  mocha.run()
}

class Enum {
  constructor(values) {
    this.__values = []
    let isObject = arguments.length === 1
    let args = isObject ? Object.keys(values) : [...arguments]
    args.forEach((name, index) => {
      this.__createValue(name, isObject ? values[name] : null, index)
    })
    Object.freeze(this)
  }

  values() {
    return this.__values
  }

  /* @private */
  __createValue(name, props, index) {
    let value = new Object()
    value.__defineGetter__('name', function() {
      return Symbol(name)
    })
    value.__defineGetter__('ordinal', function() {
      return index
    })
    if (props) {
      Object.keys(props).forEach(prop => {
        value.__defineGetter__(prop, function() {
          return props[prop]
        })
        value.__proto__['get' + this.__capitalize(prop)] = function() {
          return this[prop]
        }
      })
    }
    Object.defineProperty(this, name, {
      value: Object.freeze(value),
      writable: false
    })
    this.__values.push(this[name])
  }

  /* @private */
  __capitalize(str) {
    return str.charAt(0).toUpperCase() + str.slice(1)
  }
}

main()
.as-console-wrapper {
  top: 0;
  max-height: 100% !important;
}
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.2.5/mocha.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mocha/2.2.5/mocha.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.2.0/chai.js"></script>
<!--

public enum Color {
  RED("#F00"),
  BLUE("#0F0"),
  GREEN("#00F");
  
  private String hex;
  public String getHex()  { return this.hex;  }
  
  private Color(String hex) {
    this.hex = hex;
  }
}

-->
<div id="mocha"></div>


-3

Ви можете використовувати ES6 Map

const colors = new Map([
  ['RED', 'red'],
  ['BLUE', 'blue'],
  ['GREEN', 'green']
]);

console.log(colors.get('RED'));

IMHO - це погане рішення через його складність (слід викликати метод аксесуара кожен раз) та контрдикацію характеру enum (може викликати мутаційний метод і змінити значення будь-якого ключа) ... тому використовуйте const x = Object.freeze({key: 'value'})натомість, щоб отримати щось, що виглядає і поводиться як енюм в ES6
Юрій Рабешко

Ви повинні пройти рядок, щоб отримати значення, як це робили color.get ("ЧЕРВЕНО"). Що схильне до помилок.
adrian oviedo
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.