Enums in Javascript with ES6

前端 未结 15 1837
青春惊慌失措
青春惊慌失措 2020-12-12 10:42

I\'m rebuilding an old Java project in Javascript, and realized that there\'s no good way to do enums in JS.

The best I can come up with is:

const C         


        
相关标签:
15条回答
  • 2020-12-12 11:20

    Here is my implementation of a Java enumeration in JavaScript.

    I also included unit tests.

    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>

    0 讨论(0)
  • 2020-12-12 11:22

    You can check Enumify, a very good and well featured library for ES6 enums.

    0 讨论(0)
  • 2020-12-12 11:24

    Check how TypeScript does it. Basically they do the following:

    const MAP = {};
    
    MAP[MAP[1] = 'A'] = 1;
    MAP[MAP[2] = 'B'] = 2;
    
    MAP['A'] // 1
    MAP[1] // A
    

    Use symbols, freeze object, whatever you want.

    0 讨论(0)
  • 2020-12-12 11:25

    I prefer @tonethar's approach, with a little bit of enhancements and digging for the benefit of understanding better the underlyings of the ES6/Node.js ecosystem. With a background in the server side of the fence, I prefer the approach of functional style around platform's primitives, this minimizes the code bloat, the slippery slope into the state's management valley of the shadow of death due to the introduction of new types and increases the readability - makes more clear the intent of the solution and the algorithm.

    Solution with 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
    
    0 讨论(0)
  • 2020-12-12 11:28

    As mentioned above, you could also write a makeEnum() helper function:

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

    Use it like this:

    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");
    }
    
    0 讨论(0)
  • 2020-12-12 11:29

    Update 11.05.2020:
    Modified to include static fields and methods to closer replicate "true" enum behavior.

    If you're planning on updating I would recommend trying to use what I call an "Enum Class" (barring any browser or runtime env limitations you can't accept). It's basically a very simple and clean class that uses private fields and limited accessors to simulate the behavior of an enum. This is something I sometimes do in C# when I want to build more functionality into an enum.

    I realize private class fields are still experimental at this point but it seems to work for the purposes of creating a class with immutable fields/properties. Browser support is decent as well. The only "major" browsers that don't support it are Firefox (which I'm sure they will soon) and IE (who cares).

    DISCLAIMER:
    I am not a developer. I just put this together to solve the limitations of nonexistent enums in JS when I was working on a personal project.

    Sample Class

    class Colors {
        // Private Fields
        static #_RED = 0;
        static #_GREEN = 1;
        static #_BLUE = 2;
    
        // Accessors for "get" functions only (no "set" functions)
        static get RED() { return this.#_RED; }
        static get GREEN() { return this.#_GREEN; }
        static get BLUE() { return this.#_BLUE; }
    }
    

    You should now be able to call your enums directly.

    Colors.RED; // 0
    Colors.GREEN; // 1
    Colors.BLUE; // 2
    

    The combination of using private fields and limited accessors means that the existing enum values are well protected (they're essentially constants now).

    Colors.RED = 10 // Colors.RED is still 0
    Colors._RED = 10 // Colors.RED is still 0
    Colors.#_RED = 10 // Colors.RED is still 0
    
    0 讨论(0)
提交回复
热议问题