查看原文
其他

TypeScript 枚举类型

semlinker 全栈修仙之路 2021-01-15

使用枚举我们可以定义一些带名字的常量。枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript 支持数字的和基于字符串的枚举。

一、数字枚举

在 TypeScript 中可以通过 enum 关键字来定义枚举,比如:

  1. enum RequestMethod {

  2. Get,

  3. Post,

  4. Put,

  5. Delete,

  6. Options,

  7. Head,

  8. Patch

  9. }


  10. let requestMethod = RequestMethod.Get;

  11. console.log(requestMethod); // 0

以上代码定义了 RequestMethod 枚举,用于表示 HTTP 中常见的请求方法。因为 JavaScript 中并没有存在枚举类型,因此为了能够在大多数浏览器中正常运行,上面定义的 RequestMethod 枚举会被编译成以下 ES5 代码:

  1. "use strict";

  2. var RequestMethod;

  3. (function (RequestMethod) {

  4. RequestMethod[RequestMethod["Get"] = 0] = "Get";

  5. RequestMethod[RequestMethod["Post"] = 1] = "Post";

  6. RequestMethod[RequestMethod["Put"] = 2] = "Put";

  7. RequestMethod[RequestMethod["Delete"] = 3] = "Delete";

  8. RequestMethod[RequestMethod["Options"] = 4] = "Options";

  9. RequestMethod[RequestMethod["Head"] = 5] = "Head";

  10. RequestMethod[RequestMethod["Patch"] = 6] = "Patch";

  11. })(RequestMethod || (RequestMethod = {}));

  12. var requestMethod = RequestMethod.Get;

  13. console.log(requestMethod);

通过观察上述生成的 ES5 代码,我们发现还可以通过数值索引来反向访问 RequestMethod 枚举的成员:

  1. console.log(RequestMethod[0]) // "Get"

因为在定义 RequestMethod 枚举时,没有使用初始化器,因此 Get 的值为 0, Post 的值为 1,依次类推。当然,你也可以根据实际需求来调整初始值,比如:

  1. enum RequestMethod {

  2. Get = 6,

  3. Post,

  4. Put,

  5. Delete,

  6. Options,

  7. Head,

  8. Patch

  9. }

或者设置中间枚举成员的值:

  1. enum RequestMethod {

  2. Get,

  3. Post,

  4. Put,

  5. Delete = 8,

  6. Options,

  7. Head,

  8. Patch

  9. }

以上代码编译生成的 ES5 代码如下:

  1. "use strict";

  2. var RequestMethod;

  3. (function (RequestMethod) {

  4. RequestMethod[RequestMethod["Get"] = 0] = "Get";

  5. RequestMethod[RequestMethod["Post"] = 1] = "Post";

  6. RequestMethod[RequestMethod["Put"] = 2] = "Put";

  7. RequestMethod[RequestMethod["Delete"] = 8] = "Delete";

  8. RequestMethod[RequestMethod["Options"] = 9] = "Options";

  9. RequestMethod[RequestMethod["Head"] = 10] = "Head";

  10. RequestMethod[RequestMethod["Patch"] = 11] = "Patch";

  11. })(RequestMethod || (RequestMethod = {}));

通过观察生成的 ES5 代码可知,默认还是从 0 开始,当发现中间成员重新定义了枚举的初始值,下一个值将从新的初始值开始递增,每次的增量为 1。

利用这个特性,在确保不出现冲突的提前下,我们还可以合并在不同文件中定义的相同名称的枚举或分开定义枚举。

  1. enum RequestMethod {

  2. Get,

  3. Post,

  4. Put

  5. }


  6. enum RequestMethod {

  7. Delete = 8,

  8. Options,

  9. Head,

  10. Patch

  11. }


  12. console.log(JSON.stringify(RequestMethod, null, '\t'));

以上代码成功编译并正常运行后,控制台会输出以下内容:

  1. {

  2. "0": "Get",

  3. "1": "Post",

  4. "2": "Put",

  5. "8": "Delete",

  6. "9": "Options",

  7. "10": "Head",

  8. "11": "Patch",

  9. "Get": 0,

  10. "Post": 1,

  11. "Put": 2,

  12. "Delete": 8,

  13. "Options": 9,

  14. "Head": 10,

  15. "Patch": 11

  16. }

这里需要注意的是,枚举成员可以使用常量枚举表达式进行初始化。常量枚举表达式是 JavaScript 表达式的子集,它可以在编译阶段求值。当一个表达式满足下面条件之一时,它就是一个常量枚举表达式:

  • 一个枚举表达式字面量(主要是字符串字面量或数字字面量);

  • 一个对之前定义的常量枚举成员的引用(可以是在不同的枚举类型中定义的);

  • 带括号的常量枚举表达式;

  • 一元运算符 +-~ 其中之一应用在了常量枚举表达式;

  • 常量枚举表达式做为二元运算符 +-*/%<<>>>>>&|^ 的操作对象。若常量枚举表达式求值后为 NaN或 Infinity,则会在编译阶段报错。

为了便于大家理解,我们来看一个常量枚举表达式的实际例子:

  1. const enum SlidingState {

  2. Disabled = 1 << 1,

  3. Enabled = 1 << 2,

  4. End = 1 << 3,

  5. Start = 1 << 4,


  6. SwipeEnd = 1 << 5,

  7. SwipeStart = 1 << 6,

  8. }

二、字符串枚举

在 TypeScript 2.4 版本中,引入了一个众人期待的特性 —— 字符串枚举。字符串枚举的概念很简单,在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。同样,我们仍然使用 enum 关键字来定义字符串枚举,具体示例如下:

  1. enum MediaTypes {

  2. JSON = "application/json",

  3. XML = "application/xml"

  4. }

与分析数字枚举一样,我们来看一下 MediaTypes 字符串枚举编译生成的 ES5 代码:

  1. "use strict";

  2. var MediaTypes;

  3. (function (MediaTypes) {

  4. MediaTypes["JSON"] = "application/json";

  5. MediaTypes["XML"] = "application/xml";

  6. })(MediaTypes || (MediaTypes = {}));

这意味着我们可以通过键的值来解析值,但是不能通过键的值来解析键:

  1. MediaTypes["JSON"]; // "application/json"

  2. MediaTypes["application/json"]; // undefined


  3. MediaTypes["XML"]; // "application/xml"

  4. MediaTypes["application/xml"]; // undefined

而对于数字枚举来说,数字枚举可以实现反向映射:

  1. enum DefaultPorts {

  2. HTTP = 80,

  3. HTTPS = 443

  4. }


  5. DefaultPorts["HTTP"]; // 80

  6. DefaultPorts[80]; // "HTTP"


  7. DefaultPorts["HTTPS"]; // 443

  8. DefaultPorts[443]; // "HTTPS"

三、const 枚举

大多数情况下,枚举是十分有效的方案,然而在某些情况下需求很严格。为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用 const 枚举。

常量枚举通过在枚举上使用 const 修饰符来定义:

  1. const enum RequestMethod {

  2. Get,

  3. Post,

  4. Put,

  5. Delete,

  6. Options,

  7. Head,

  8. Patch

  9. }


  10. let methods = [RequestMethod.Get, RequestMethod.Post, RequestMethod.Put]

以上代码经编译后生成的 ES5 代码如下:

  1. "use strict";

  2. var methods = [0 /* Get */, 1 /* Post */, 2 /* Put */];

很明显使用 const 修饰符后,编译器将不会为我们的 RequestMethod 枚举生成任何映射代码。相反,它将在所有使用的地方,内联每个枚举成员的值,从而可能节省一些字节和属性访问间接性的开销。

但有些时候,我们还是希望能生成映射代码,针对这种情况,我们可以设置 preserveConstEnums 编译器选项为 true:

  1. {

  2. "compilerOptions": {

  3. "target": "es5",

  4. "preserveConstEnums": true

  5. }

  6. }

启用 preserveConstEnums 配置后,再次编译上面的代码,这时将会重新生成映射代码,具体如下:

  1. "use strict";

  2. var RequestMethod;

  3. (function (RequestMethod) {

  4. RequestMethod[RequestMethod["Get"] = 0] = "Get";

  5. RequestMethod[RequestMethod["Post"] = 1] = "Post";

  6. RequestMethod[RequestMethod["Put"] = 2] = "Put";

  7. RequestMethod[RequestMethod["Delete"] = 3] = "Delete";

  8. RequestMethod[RequestMethod["Options"] = 4] = "Options";

  9. RequestMethod[RequestMethod["Head"] = 5] = "Head";

  10. RequestMethod[RequestMethod["Patch"] = 6] = "Patch";

  11. })(RequestMethod || (RequestMethod = {}));


  12. var methods = [0 /* Get */, 1 /* Post */, 2 /* Put */];

四、参考资源

  • mariusschulz —— string-enums-in-typescript

  • tslang —— enums

往期精彩回顾
常用消息摘要算法简介
一文读懂Base64编码

TypeScript 设计模式之适配器模式

布隆过滤器你值得拥有的开发利器

在前端 Word 还能这样玩


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存