详解TypeScript中枚举对象(Enum)

什么是枚举,顾名思义我们看到枚举这个词,脑子里就已经想到这是一个键值对的形式,就可以看做是我们JavaScript中的json对象一样。在ts中声明枚举对象的关键字是使用enum,枚举成员一但定义就不可改变了。下面来介绍一下ts中的枚举对象。

数字枚举

我们先来说一下数字枚举, 一目了然,一看就知道数字枚举的成员都是number类型的。下面我们来看看创建一个简单的枚举对象吧。

enum device {
    phone,
    notebook,
    desktop
}

console.log(device.phone) // 0

上面的值是phone 0、notebook 1、desktop 2。

只要定义了枚举对象,默认不赋值,那么当前的枚举对象里面第一个的值从0开始依次递增。

看下面栗子

enum device {
  phone = 3,
  notebook,
  desktop
}

上面我们默认给phone赋值一个3,那么下面两个值也会默认递增,会变成notebook = 4、desktop = 5。

注意事项 - 枚举对象递增

  • 枚举对象成员递增值为1
  • 枚举对象成员递增只会看当前值的前一个枚举成员是否有值,有值的话后面依次跟着递增。跟第一个枚举成员值无关

数字枚举对象会存在反向映射

enum device {
      phone
      notebook,
      desktop
}

通过上面栗子我们可以知道,device.notebook = 1。但还可以通过device[1]获取出来notebook,这就是因为存在反向映射(key和value可以互相访问)。

需要注意的是,只有数字枚举成员才会有反向映射,字符串或其它值是没有的。

来看下枚举对象被编译后的

我们再来看一个问题

enum device {
    phone = 2,
    notebook = 1,
    desktop
}

console.log(device.phone) // 2
console.log(device[2]) // desktop

可以看到上面代码中,device.phone = 2 ,然后我们使用反向这种方式访问device[2]却是desktop。这是因为默认我们给phone赋值为2,然后又给notebook赋值为1,这时desktop跟着上一个枚举成员(notebook)值去递增,所以这时2已经被替换为desktop了。

这里需要注意,避免踩坑,ts是不会检查出来的重复的值。

字符串枚举

看过了上面的数字枚举,再来看字符串枚举,显然就明白字符串枚举成员的值肯定都是string类型。字符串枚举对象是没有反向映射的。

enum device {
    phone = "1",
    notebook = "2",
    desktop = "3"
}

我们来看一下编译后的代码

可以看到上面编译后的代码没有反向映射的代码。

字符串枚举是没有递增的,当前的枚举成员前一个值为字符串,那么当前的枚举对象如果不赋值就会报错。

enum device {
    phone = "1",
    notebook
}

上面这种情况就会导致编译阶段报错。"枚举成员必须具有初始化表达式",待会我们就会讲到枚举对象的表达式。

异构枚举

异构枚举是啥呢,直白的说就是一个枚举对象中可以包括数字枚举成员和字符串枚举成员,就是可以混合使用。但是我们要正儿八经的使用枚举对象的话,一般还真不会使用异构枚举,就像文档说的一样,似乎你并不会这么做。

enum Person {
    name = "前端娱乐圈",
    age = 18
}

在看一个栗子

enum Person {
    name = "前端娱乐圈",
    age = 3 * 6
}

上面这种是会报错的,“含字符串值成员的枚举中不允许使用计算值”,枚举对象成员有字符串的则不能再设置其它枚举对象成员为计算的值(3 * 6)。但是可以直接写字面量的。下面我们会讲到计算

计算的和常量成员

枚举对象中的枚举成员都带有一个值,这个值是计算的或常量。那么怎么看是计算的还是常量呢。这就讲到我们上面说的枚举对象成员表达式,只要是表达式那一定就是常量否则就是计算的。所以只需要知道枚举成员是表达式就知道它就是常量。

当满足下面其中一个条件那么它就是一个表达式。以下借用官方的规则条件

  • 一个枚举表达式字面量(主要是字符串字面量或数字字面量)
  • 一个对之前定义的常量枚举成员的引用(可以是在不同的枚举类型中定义的)
  • 带括号的常量枚举表达式
  • 一元运算符 +, -, ~其中之一应用在了常量枚举表达式
  • 常量枚举表达式做为二元运算符 +, -, *, /, %, <<, >>, >>>, &, |, ^的操作对象。 若常数枚举表达式求值后为 NaN或 Infinity,则会在编译阶段报错。

上面这些条件成立之后那么当前枚举成员就是一个常量。常量就是可以在编译阶段求值的

常量

enum obj {
    index, // 满足条件 常量
    index1 = index, // 满足条件 常量
    age = 2 << 1, // 满足条件 常量 
    num = 30 | 2, // 满足条件 常量
    num1 = 10 + 29 // 满足条件 常量
}

在看一下上面编译后的代码,可以看到直接在编译阶段求值了。

计算的

enum obj {
    nameLen = "前端娱乐圈".length, // 计算的
    num = Math.random() * 100 // 计算的
}

在看一下计算的编译后的代码是没有求值的。

const枚举

一般情况下,普通枚举对象就可以满足我的需求,但是有些情况比如为了节省额外的开销和性能,我们可以选择使用常量枚举,常量枚举使用const关键字定义,它与普通枚举不同的时,它会在编译阶段删除该对象,且不能访问该枚举对象,只能访问该枚举对象成员。常量枚举的成员只能是常量枚举表达式,不可以使用计算值

const enum obj {
    A = 1,
    B = 3 * 6,
    C = 1 & 2
}

console.log(obj) // 报错
需要注意上面这个常量枚举对象编译后的对象也是"空"的,系统自动删除
const enum obj {
    A = 1,
    B = 3 * 6,
    C = 1 & 2
}

console.log(obj.A) // 1
console.log(obj.B) // 8
console.log(obj.C) // 0

那么上面这个栗子,编译后只能看见console.log这些值。

外部枚举

外部枚举使用declare关键字定义,文档描述:外部枚举用来描述已经存在的枚举类型的形状,意思就是说外部枚举用来描述当前环境中存在的枚举对象。外部枚举和普通枚举的一个区别就是,在外部枚举里面没有初始化的枚举成员会当成一个计算值,而在普通枚举里面则是一个常量。

declare enum Enum {
    A = 1,
    B,
    C = 2
}

console.log(Enum);
console.log(Enum.A)

上面这种执行完,你会发现不管执行枚举本身还是枚举成员都是报错,"Enum is not defined"。因为外部枚举编译后压根就没有生成。所以有明白外部枚举用途的小伙伴给解答一下,谢谢啦~

枚举成员的类型

枚举成员也可以被当做一个类型,就是可以指定某些变量的值必须是枚举成员的值。

枚举成员成为类型必须满足其中之一条件

  • 字面量枚举成员是指不带有初始值的常量枚举成员
  • 任何字符串字面量(如:A、B)
  • 任何数字字面量(如: 1, 100)
  • 应用了一元 -符号的数字字面量(如: -1, -100)
enum obj {
    name = "前端娱乐圈",
    num = -100
}

interface msg {
    title: obj.name;
    num: obj.num
}

let json: msg = {
    title: obj.name,
    num: obj.num
}

在 Go 语言中总是有一些看上去奇奇怪怪的东西,其中就包括 SliceHeader 和 StringHeader 结构体。SliceHeaderSliceHeader 如其名, ...