ES6 ile Javascriptteki numaralandırmalar


136

Javascript'te eski bir Java projesi oluşturuyorum ve JS'de numaralandırma yapmanın iyi bir yolu olmadığını fark ettim.

En iyi ben gelebilir:

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

constTutan Colorsyeniden edilmesini ve bu anahtarlar ve değerler mutasyona önler dondurulması. Sembolleri kullanıyorum Colors.RED, bu eşit değil 0ya da başka bir şey değil.

Bu formülasyonda bir problem var mı? Daha iyi bir yol var mı?


(Bu sorunun biraz tekrarlandığını biliyorum, ancak önceki Q / As'ın tamamı oldukça eski ve ES6 bize bazı yeni yetenekler veriyor.)


DÜZENLE:

Serileştirme sorunu ile ilgilenen başka bir çözüm, ancak hala bölge sorunları olduğuna inanıyorum:

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

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

Değer olarak nesne referanslarını kullanarak, Sembollerle aynı çarpışmadan kaçınma elde edersiniz.


2
bu es6'da mükemmel bir yaklaşım olacaktır. Dondurmak zorunda değilsiniz
Nirus

2
@Nirus, değiştirilmesini istemiyorsanız yaparsınız.
zerkms

2
Bu cevabı fark ettiniz mi?
Bergi

3
Düşünebileceğim bir konu: Bu numaralandırmayı kullanamıyorum JSON.stringify(). Serileştirilemez / serileştirilemez Symbol.
le_m

1
@ErictheRed Yıllardır herhangi bir sorun yaşamadan string enum sabit değerleri kullanıyorum, çünkü Flow (veya TypeScript) kullanmak çarpışmadan kaçınma konusunda üzülmekten çok daha fazla tip güvenliği garanti ediyor
Andy

Yanıtlar:


131

Bu formülasyonda bir problem var mı?

Ben görmüyorum.

Daha iyi bir yol var mı?

İki ifadeyi bire daraltırdım:

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

Boilerplate'i sevmiyorsanız, tekrarlanan Symbolçağrılar gibi, elbette makeEnumbir isim listesinden aynı şeyi yaratan bir yardımcı işlev de yazabilirsiniz .


3
Burada bölge sorunları yok mu?

2
@torazaburo Kod iki kez yüklendiğinde, farklı semboller üretecektir, bu da dizelerle ilgili bir sorun olmayacaktır? Evet, iyi bir nokta, cevap ver :-)
Bergi

2
@ErictheRed Hayır, Symbol.foryok değil çapraz bölge sorunları var, ancak bir ile olağan çarpışma sorunu var gerçekten global ad .
Bergi

1
@ErictheRed Ne zaman ve nerede (bölge / çerçeve / sekme / süreç denir) ne olursa olsun tam olarak aynı sembolü yaratmayı garanti eder
Bergi

1
@jamesemanon İsterseniz açıklamayı alabilirsiniz , ancak öncelikle hata ayıklamak için kullanırım. Bunun yerine, her zamanki gibi özel bir numaradan dizeye dönüştürme işlevi vardır (satır boyunca bir şey enum => ({[Colors.RED]: "bright red", [Colors.BLUE]: "deep blue", [Colors.GREEN]: "grass green"}[enum])).
Bergi

18

Kullanarak iken Symbolsıralama değeri basit kullanım durumları için çalışıyor olarak, çeteleler için özellikler kazandırmak için kullanışlı olabilir. Bu Object, özellikleri içeren bir enum değeri olarak kullanılarak yapılabilir .

Örneğin, her birine Colorsbir ad ve onaltılık değer verebiliriz :

/**
 * 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'a özellikler eklemek switchifadeler yazmak zorunda kalmaz (ve bir enum genişletildiğinde anahtar ifadelerine yeni vakaları unutmaktan) kaçınır . Örnek ayrıca JSDoc enum ek açıklamasıyla belgelenen enum özelliklerini ve türlerini gösterir .

Eşitlik, Colors.RED === Colors.REDvarlık trueve Colors.RED === Colors.BLUEvarlık ile beklendiği gibi çalışır false.


9

Yukarıda belirtildiği gibi, bir makeEnum()yardımcı işlev de yazabilirsiniz :

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

Şöyle kullanın:

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
Tek astar olarak: const makeEnum = (...lst) => Object.freeze(Object.assign({}, ...lst.map(k => ({[k]: Symbol(k)})))); O zaman şu şekilde kullanın const colors = makeEnum("Red", "Green", "Blue")
Manuel Ebert

9

Bu benim kişisel yaklaşımım.

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

Tüm olası değerleri yinelemek için bir yol sağlar ve her bir değer el ile kontrol etmeden bir değer bir ColorType olup olmadığını kontrol etmek için hiçbir şekilde sağladığı gibi bunu kullanmanızı tavsiye etmem.
Domino

7

TypeScript'in bunu nasıl yaptığını kontrol edin . Temel olarak aşağıdakileri yaparlar:

const MAP = {};

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

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

Sembolleri kullanın, nesneyi dondurun, ne istersen.


Bunun MAP[MAP[1] = 'A'] = 1;yerine neden kullandığını takip etmiyorum MAP[1] = 'A'; MAP['A'] = 1;. Her zaman bir ödevin ifade olarak kullanılmasının kötü bir stil olduğunu duydum. Ayrıca, yansıtılmış ödevlerden ne gibi faydalar elde edersiniz?
Eric the

1
İşte enum eşlemenin dokümanlarındaki es5 ile nasıl derlendiğini gösteren bir bağlantı. typescriptlang.org/docs/handbook/enums.html#reverse-mappings Tek bir satıra derlemek daha kolay ve daha özlü bir görüntü olabilir örneğin MAP[MAP[1] = 'A'] = 1;.
givehug

Huh. Bu yüzden yansıtma, her bir değerin dize ve sayı / simge gösterimleri arasında geçiş yapmayı kolaylaştırır ve bazı dize veya sayı / sembollerin xgeçerli bir Enum değeri olup olmadığını kontrol eder Enum[Enum[x]] === x. Orijinal sorunlarımdan herhangi birini çözmez, ancak yararlı olabilir ve hiçbir şeyi bozmaz.
Kırmızı Eric

1
TypeScript'in, TS kodu derlendiğinde kaybolan bir sağlamlık katmanı eklediğini unutmayın. Tüm uygulamanız TS'de yazılıyorsa harika, ancak JS kodunun sağlam olmasını istiyorsanız, dondurulmuş sembol haritası daha güvenli bir desen gibi geliyor.
Domino



1

Belki bu çözüm? :)

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

Misal:

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

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

bir kullanım örneği gerçekten takdir edilecektir :-)
Abderrahmane TAHRI JOUTI

0

ES6 / Node.js ekosisteminin temelini daha iyi anlamak için @ tonethar'ın yaklaşımını, biraz iyileştirme ve kazma ile tercih ediyorum. Çitin sunucu tarafında bir arka plan ile, platformun ilkelleri etrafında işlevsel stil yaklaşımını tercih ederim, bu yeni tasarımların ortaya çıkması ve artması nedeniyle devletin ölüm gölgesindeki kaygan eğimi, kod bloatını en aza indirir. okunabilirlik - çözümün ve algoritmanın amacını daha açık hale getirir.

İle Çözüm TDD , ES6 , node.js , Lodash , Jest , Babil , 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))kesinlikle hiçbir şey yapmaz. Sadece ...argsdoğrudan kullanabilirsiniz .
Domino

0

İşte bazı yardımcı yöntemler de dahil olmak üzere yaklaşımım

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

İşte JavaScript Java numaralandırma uygulaması.

Ayrıca birim testleri de dahil ettim.

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 Haritasını kullanabilirsiniz

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

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

IMHO kullanmak yani ... (Mutator yöntemini çağırın ve herhangi anahtarın bir değeri değiştirebilirsiniz) enum doğanın ve сontradiction (erişimci yöntemini her zaman çağırmalıdır) karmaşıklığı nedeniyle kötü bir çözüm var const x = Object.freeze({key: 'value'})bir şey almak için bunun yerine bakar ve ES6'da enum gibi davranıyor
Yurii Rabeshko

Colours.get ('RED') gibi değeri almak için bir dize iletmeniz gerekir. Hangi hata eğilimli.
adrian oviedo
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.