以下内容是否会使对象满足枚举在JavaScript中的所有特性?类似于:

my.namespace.ColorEnum = {
  RED : 0,
  GREEN : 1,
  BLUE : 2
}

// later on

if(currentColor == my.namespace.ColorEnum.RED) {
  // whatever
}

还是有其他方法可以做到这一点?


当前回答

var DaysEnum = Object.freeze ({ monday: {}, tuesday: {}, ... });

您不需要指定id,只需使用空对象来比较枚举即可。

if (incommingEnum === DaysEnum.monday) //incommingEnum is monday

EDIT:如果要序列化对象(例如JSON),将再次输入id。

(摘自加布里埃尔·利亚马斯评论)(根据Stijn de Witt的评论进行编辑)

其他回答

在我看来,枚举是什么?它是一个不可变的对象,总是可以访问,您可以相互比较项目,但项目具有通用的财产/方法,但对象本身或值不能更改,它们只能实例化一次。

枚举用于比较数据类型、设置、采取/回复的操作等。

为此,您需要具有相同实例的对象,以便您可以检查它是否为枚举类型if(something instanceof enum)此外,如果您获得了一个枚举对象,则无论枚举类型如何,它都应该以相同的方式进行响应。

在我的例子中,它比较数据类型的值,但它可以是任何东西,从在3d游戏中修改面向方向的块到将值传递到特定的对象类型注册表。

请记住,它是javascript,不提供固定的枚举类型,您最终总是自己实现,正如本线程所示,有很多实现都不正确。


这是我用于枚举的内容。由于枚举是不可变的(或者至少应该是呵呵),所以我冻结了对象,这样它们就不容易被操作了。

枚举可以由EnumField.STRING使用,并且它们有自己的方法来处理它们的类型。要测试是否有东西传递给对象,可以使用if(somevar instanceof EnumFieldSegment)

这可能不是最优雅的解决方案,我愿意改进,但这种类型的不可变枚举(除非你解冻它)正是我需要的用例。

我也意识到我可以用{}重写原型,但我的大脑在这种格式下工作得更好;-)向我开枪。

/**
 * simple parameter object instantiator
 * @param name
 * @param value
 * @returns
 */
function p(name,value) {
    this.name = name;
    this.value = value;
    return Object.freeze(this);
}
/**
 * EnumFieldSegmentBase
 */
function EnumFieldSegmentBase() {
    this.fieldType = "STRING";
}
function dummyregex() {
}
dummyregex.prototype.test = function(str) {
    if(this.fieldType === "STRING") {
        maxlength = arguments[1];
        return str.length <= maxlength;
    }
    return true;
};

dummyregexposer = new dummyregex();
EnumFieldSegmentBase.prototype.getInputRegex = function() { 
    switch(this.fieldType) {
        case "STRING" :     return dummyregexposer;  
        case "INT":         return /^(\d+)?$/;
        case "DECIMAL2":    return /^\d+(\.\d{1,2}|\d+|\.)?$/;
        case "DECIMAL8":    return /^\d+(\.\d{1,8}|\d+|\.)?$/;
        // boolean is tricky dicky. if its a boolean false, if its a string if its empty 0 or false its  false, otherwise lets see what Boolean produces
        case "BOOLEAN":     return dummyregexposer;
    }
};
EnumFieldSegmentBase.prototype.convertToType = function($input) {
    var val = $input;
    switch(this.fieldType) {
        case "STRING" :         val = $input;break;
        case "INT":         val==""? val=0 :val = parseInt($input);break;
        case "DECIMAL2":    if($input === "" || $input === null) {$input = "0"}if($input.substr(-1) === "."){$input = $input+0};val = new Decimal2($input).toDP(2);break;
        case "DECIMAL8":    if($input === "" || $input === null) {$input = "0"}if($input.substr(-1) === "."){$input = $input+0};val = new Decimal8($input).toDP(8);break;
        // boolean is tricky dicky. if its a boolean false, if its a string if its empty 0 or false its  false, otherwise lets see what Boolean produces
        case "BOOLEAN":     val = (typeof $input == 'boolean' ? $input : (typeof $input === 'string' ? (($input === "false" || $input === "" || $input === "0") ? false : true) : new Boolean($input).valueOf()))  ;break;
    }
    return val;
};
EnumFieldSegmentBase.prototype.convertToString = function($input) {
    var val = $input;
    switch(this.fieldType) {
        case "STRING":      val = $input;break;
        case "INT":         val = $input+"";break;
        case "DECIMAL2":    val = $input.toPrecision(($input.toString().indexOf('.') === -1 ? $input.toString().length+2 : $input.toString().indexOf('.')+2)) ;break;
        case "DECIMAL8":    val = $input.toPrecision(($input.toString().indexOf('.') === -1 ? $input.toString().length+8 : $input.toString().indexOf('.')+8)) ;break;
        case "BOOLEAN":     val = $input ? "true" : "false"  ;break;
    }
    return val;
};
EnumFieldSegmentBase.prototype.compareValue = function($val1,$val2) {
    var val = false;
    switch(this.fieldType) {
        case "STRING":      val = ($val1===$val2);break;
        case "INT":         val = ($val1===$val2);break;
        case "DECIMAL2":    val = ($val1.comparedTo($val2)===0);break;
        case "DECIMAL8":    val = ($val1.comparedTo($val2)===0);break;
        case "BOOLEAN":     val = ($val1===$val2);break;
    }
    return val;
};

/**
 * EnumFieldSegment is an individual segment in the 
 * EnumField
 * @param $array An array consisting of object p
 */
function EnumFieldSegment() {
    for(c=0;c<arguments.length;c++) {
        if(arguments[c] instanceof p) {
            this[arguments[c].name] = arguments[c].value;
        }
    }
    return Object.freeze(this); 
}
EnumFieldSegment.prototype = new EnumFieldSegmentBase();
EnumFieldSegment.prototype.constructor = EnumFieldSegment;


/**
 * Simple enum to show what type of variable a Field type is.
 * @param STRING
 * @param INT
 * @param DECIMAL2
 * @param DECIMAL8
 * @param BOOLEAN
 * 
 */
EnumField = Object.freeze({STRING:      new EnumFieldSegment(new p("fieldType","STRING")), 
                            INT:        new EnumFieldSegment(new p("fieldType","INT")), 
                            DECIMAL2:   new EnumFieldSegment(new p("fieldType","DECIMAL2")), 
                            DECIMAL8:   new EnumFieldSegment(new p("fieldType","DECIMAL8")), 
                            BOOLEAN:    new EnumFieldSegment(new p("fieldType","BOOLEAN"))});

es7方式,(迭代器,冻结),用法:

const ThreeWiseMen = new Enum('Melchior', 'Caspar', 'Balthazar')

for (let name of ThreeWiseMen)
    console.log(name)


// with a given key
let key = ThreeWiseMen.Melchior

console.log(key in ThreeWiseMen) // true (string conversion, also true: 'Melchior' in ThreeWiseMen)

for (let entry from key.enum)
     console.log(entry)


// prevent alteration (throws TypeError in strict mode)
ThreeWiseMen.Me = 'Me too!'
ThreeWiseMen.Melchior.name = 'Foo'

代码:

class EnumKey {

    constructor(props) { Object.freeze(Object.assign(this, props)) }

    toString() { return this.name }

}

export class Enum {

    constructor(...keys) {

        for (let [index, key] of keys.entries()) {

            Object.defineProperty(this, key, {

                value: new EnumKey({ name:key, index, enum:this }),
                enumerable: true,

            })

        }

        Object.freeze(this)

    }

    *[Symbol.iterator]() {

        for (let key of Object.keys(this))
            yield this[key]

    }

    toString() { return [...this].join(', ') }

}

更新

我认为我下面的答案不再是用JavaScript编写枚举的最佳方法。有关更多详细信息,请参阅我的博客文章:JavaScript中的枚举。


已经可以提醒名称:

if (currentColor == my.namespace.ColorEnum.RED) {
   // alert name of currentColor (RED: 0)
   var col = my.namespace.ColorEnum;
   for (var name in col) {
     if (col[name] == col.RED)
       alert(name);
   }
}

或者,您可以创建值对象,这样您就可以吃蛋糕了:

var SIZE = {
  SMALL : {value: 0, name: "Small", code: "S"}, 
  MEDIUM: {value: 1, name: "Medium", code: "M"}, 
  LARGE : {value: 2, name: "Large", code: "L"}
};

var currentSize = SIZE.MEDIUM;
if (currentSize == SIZE.MEDIUM) {
  // this alerts: "1: Medium"
  alert(currentSize.value + ": " + currentSize.name);
}

在JavaScript中,因为它是一种动态语言,所以以后甚至可以向集合中添加枚举值:

// Add EXTRALARGE size
SIZE.EXTRALARGE = {value: 3, name: "Extra Large", code: "XL"};

记住,枚举的字段(本例中的值、名称和代码)不需要用于身份检查,只是为了方便。此外,大小属性本身的名称不需要硬编码,也可以动态设置。因此,假设您只知道新枚举值的名称,您仍然可以毫无问题地添加它:

// Add 'Extra Large' size, only knowing it's name
var name = "Extra Large";
SIZE[name] = {value: -1, name: name, code: "?"};

当然,这意味着无法再进行某些假设(例如,该值表示大小的正确顺序)。

记住,在JavaScript中,对象就像一个映射或哈希表。一组名称值对。你可以在事先不了解它们的情况下循环浏览它们或以其他方式操纵它们。

实例

for (var sz in SIZE) {
  // sz will be the names of the objects in SIZE, so
  // 'SMALL', 'MEDIUM', 'LARGE', 'EXTRALARGE'
  var size = SIZE[sz]; // Get the object mapped to the name in sz
  for (var prop in size) {
    // Get all the properties of the size object, iterates over
    // 'value', 'name' and 'code'. You can inspect everything this way.        
  }
} 

顺便说一句,如果你对名称空间感兴趣,你可能想看看我为JavaScript提供的简单但强大的名称空间和依赖关系管理的解决方案:Packages JS

我对任何答案都不满意,所以我制作了《又一个枚举》(YEA!)。

此实施:

使用最新的JS只需要这一类的声明即可轻松创建enum具有按名称(colors.RED)、字符串(colors[“RED”])和索引(colors[0])进行的映射,但只需将字符串作为数组传递将等价于String()和valueOf()函数绑定到每个enum对象(如果不希望这样做,可以简单地删除它-不过JS的开销很小)具有可选的全局命名/存储(按名称字符串)创建后冻结枚举对象,使其无法修改

特别感谢Andre‘Fi的回答给了我们一些启发。


代码:

class Enums {
  static create({ name = undefined, items = [] }) {
    let newEnum = {};
    newEnum.length = items.length;
    newEnum.items = items;
    for (let itemIndex in items) {
      //Map by name.
      newEnum[items[itemIndex]] = parseInt(itemIndex, 10);
      //Map by index.
      newEnum[parseInt(itemIndex, 10)] = items[itemIndex];
    }
    newEnum.toString = Enums.enumToString.bind(newEnum);
    newEnum.valueOf = newEnum.toString;
    //Optional naming and global registration.
    if (name != undefined) {
      newEnum.name = name;
      Enums[name] = newEnum;
    }
    //Prevent modification of the enum object.
    Object.freeze(newEnum);
    return newEnum;
  }
  static enumToString() {
    return "Enum " +
      (this.name != undefined ? this.name + " " : "") +
      "[" + this.items.toString() + "]";
  }
}

用法:

let colors = Enums.create({
  name: "COLORS",
  items: [ "RED", "GREEN", "BLUE", "PORPLE" ]
});

//Global access, if named.
Enums.COLORS;

colors.items; //Array(4) [ "RED", "GREEN", "BLUE", "PORPLE" ]
colors.length; //4

colors.RED; //0
colors.GREEN; //1
colors.BLUE; //2
colors.PORPLE; //3
colors[0]; //"RED"
colors[1]; //"GREEN"
colors[2]; //"BLUE"
colors[3]; //"PORPLE"

colors.toString(); //"Enum COLORS [RED,GREEN,BLUE,PORPLE]"

//Enum frozen, makes it a real enum.
colors.RED = 9001;
colors.RED; //0

我编写了enumeranjs一个非常小的库来解决这个问题,它确保了类型安全,允许枚举常量从原型继承,保证枚举常量和枚举类型是不可变的+许多小特性。它允许重构大量代码,并在枚举定义中移动一些逻辑。下面是一个示例:

var CloseEventCodes = new Enumeration("closeEventCodes", {
  CLOSE_NORMAL:          { _id: 1000, info: "Connection closed normally" },
  CLOSE_GOING_AWAY:      { _id: 1001, info: "Connection closed going away" },
  CLOSE_PROTOCOL_ERROR:  { _id: 1002, info: "Connection closed due to protocol error"  },
  CLOSE_UNSUPPORTED:     { _id: 1003, info: "Connection closed due to unsupported operation" },
  CLOSE_NO_STATUS:       { _id: 1005, info: "Connection closed with no status" },
  CLOSE_ABNORMAL:        { _id: 1006, info: "Connection closed abnormally" },
  CLOSE_TOO_LARGE:       { _id: 1009, info: "Connection closed due to too large packet" }
},{ talk: function(){
    console.log(this.info); 
  }
});


CloseEventCodes.CLOSE_TOO_LARGE.talk(); //prints "Connection closed due to too large packet"
CloseEventCodes.CLOSE_TOO_LARGE instanceof CloseEventCodes //evaluates to true

枚举基本上是一个工厂。

此处提供完整记录的指南。希望这有帮助。