【问题标题】:ES6 read-only enums that can map value to name可以将值映射到名称的 ES6 只读枚举
【发布时间】:2016-10-29 06:00:30
【问题描述】:

我想在JS中定义一个类似枚举的结构,但是有两个要求:

  1. 这些值是只读的,即没有用户可以分配给它们。
  2. 值 (0, 1, 2, ...) 可以映射回名称(与 Java 的 name method 一样)

我知道创建这样的枚举的方法通常满足一个要求或另一个,而不是两者。

我试过了:

const MyEnum = {
  a: 0,
  b: 1,
  c: 2
};

枚举本身是常量,但值仍然是可变的,我无法有效地将值映射回名称。

在 Typescript 中编写 enum 时,它会输出:

var MyEnum;
(function (MyEnum) {
    MyEnum[MyEnum["a"] = 0] = "a";
    MyEnum[MyEnum["b"] = 1] = "b";
    MyEnum[MyEnum["c"] = 2] = "c";
})(MyEnum || (MyEnum = {}));

这可以双向映射,但仍然没有常量值。

我发现满足这两个要求的唯一选择是在类上使用 getter:

class MyEnum {
  get a() {
    return 0;
  }
  ...
}

这种方法极大地限制了合法名称并且有很多开销,尤其是在不能很好内联(或不能)内联 getter 的浏览器中。

@Shmiddty suggested freezing an object:

const MyEnum = Object.freeze({
  a: 0,
  b: 1,
  c: 2
});

这很好地满足了常量要求,但没有提供将值映射回名称的好方法。

我可以编写一个帮助器来构建反向映射,例如:

function reverseEnum(enum) {
  Object.keys(enum).forEach(k => {
    enum[enum[k]] = k;
  });
}

但是,如果原始对象被冻结或实际上是不变的,任何生成反向映射的编程解决方案都会遇到问题。

在 JS 中是否有一个干净、简洁的解决方案?

【问题讨论】:

  • 为什么不创建枚举,添加反向属性,然后然后冻结它?尽管如此,我可以理解简洁地做到这一点的愿望。
  • @apsillers 这是个好主意。定义一个普通的 const 枚举对象,然后运行一个设置反向映射并冻结的助手。我可以很容易地保持一个电话,所以效果很好。想把它变成答案吗?
  • 是否还需要传递的具体值?我可能也会用Symbol 包装所有值,但我不知道你在期待什么。通常枚举值实际上并不重要,至少在我编写的代码中。
  • const MyEnum = Object.freeze(reverseEnum({…})) 似乎运作良好,不是吗?
  • 你可以在构造函数的末尾冻结this

标签: enums ecmascript-6


【解决方案1】:

This 做得很好,恕我直言。

function Enum(a){
  let i = Object
    .keys(a)
    .reduce((o,k)=>(o[a[k]]=k,o),{});

  return Object.freeze(
    Object.keys(a).reduce(
      (o,k)=>(o[k]=a[k],o), v=>i[v]
    )
  );
} // y u so terse?

const FOO = Enum({
  a: 0,
  b: 1,
  c: "banana"
});

console.log(FOO.a, FOO.b, FOO.c);            // 0 1 banana
console.log(FOO(0), FOO(1), FOO("banana"));  // a b c

try {
  FOO.a = "nope";
}
catch (e){
  console.log(e);
}

【讨论】:

  • //TODO: write es5 equivalent
  • 我已经使用 Ramda 库将它移植到 coffescript 下面,它应该编译成对 es5 友好的 JS,同时对匿名函数使用漂亮的箭头语法
【解决方案2】:

我会使用 Map 以便您的枚举值可以是任何类型,而不是将它们强制转换为字符串。

function Enum(obj){
    const keysByValue = new Map();
    const EnumLookup = value => keysByValue.get(value);

    for (const key of Object.keys(obj)){
        EnumLookup[key] = obj[key];
        keysByValue.set(EnumLookup[key], key);
    }

    // Return a function with all your enum properties attached.
    // Calling the function with the value will return the key.
    return Object.freeze(EnumLookup);
}

如果你的枚举都是字符串,我也可能将一行更改为:

EnumLookup[key] = Symbol(obj[key]);

以确保正确使用枚举值。仅使用一个字符串,您无法保证某些代码没有简单地传递一个恰好与您的枚举值之一相同的普通字符串。如果您的值始终是字符串或符号,您也可以将 Map 换成一个简单的对象。

【讨论】:

  • 我喜欢这个,它似乎与 Babel 的 Map 的 polyfill 配合得很好。 “如果 [me] 枚举是所有字符串”是什么意思?键还是值?
  • Symbol 会创建一个新的唯一值,因此它仅在您实际上并不关心该值是什么时才有用。我提到only strings 是因为您打算将字符串传递给Symbol('something'); 以使调试更容易。许多语言将枚举值视为不透明值,如果您在 JS 中使用类似的枚举,使用符号可能会很好。
【解决方案3】:

最近刚刚实现了一个运行良好的 Es6 版本:

const k_VALUES = {}

export class ErrorCode {

    constructor(p_apiCode, p_httpCode){
        this.apiCode = p_apiCode;
        this.httpCode = p_httpCode;

        k_VALUES[p_apiCode] = this;
    }


    static create(p_apiCode){
        if(k_VALUES[p_apiCode]){
            return k_VALUES[p_apiCode];
        }

        return ErrorCode.UNKNOWN;
    }
}

ErrorCode.UNKNOWN                 = new ErrorCode(0,     500);
ErrorCode.NOT_FOUND               = new ErrorCode(-1000, 404);
ErrorCode.NOT_FOUND_EMAIL         = new ErrorCode(-1001, 404);
ErrorCode.BAD_REQUEST             = new ErrorCode(-1010, 404);

我想实现与我们对 Java 枚举所做的类似的模式。这使我能够使用构造函数来传递值。然后构造函数冻结 ErrorCode 对象 - 既方便又方便。

用法:首先导入你的枚举类...

import {ErrorCode} from "../common/services/errors/ErrorCode";

现在,在导入枚举类之后,像这样访问它:

if( errCode.includes(ErrorCode.BAD_REQUEST.apiCode) ){...}

PS> 这与使用 Babel 的 Webpack 设置结合使用,以将我们的 ES6 类转换为浏览器兼容性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-16
    • 1970-01-01
    • 2019-04-09
    • 2019-08-14
    • 2012-05-14
    • 1970-01-01
    相关资源
    最近更新 更多