TypeScript学习:TypeScript从入门到精通
蓝桥杯真题解析:蓝桥杯Web国赛真题解析
个人简介:即将大三的学生,热爱前端,热爱生活
你的一键三连是我更新的最大动力❤️!
最近博主一直在创作
TypeScript
的内容,所有的TypeScript
文章都在我的TypeScript从入门到精通专栏里,每一篇文章都是精心打磨的优质好文,并且非常的全面和细致,期待你的订阅❤️
本篇文章将深入去讲解TypeScript
中的枚举,这也许会是你看过的最全面最细致的TypeScript
教程,点赞关注收藏不迷路!
在【TypeScript】TypeScript数据类型(下篇)中,我们了解到枚举是 TypeScript
添加到 JavaScript
的一项功能,它允许描述一个值,该值可能是一组可能的命名常量之一,接下来让我们深入去学习枚举:
一个枚举可以用 enum
关键字来定义:
enum Direction {
Up = 1,
Down,
Left,
Right,
}
上面就是一个数字枚举,其中 Up
被初始化为 1 ,所有下面的成员从这一点开始自动递增,即 Up
的值是 1 , Down
是 2 , Left
是3 , Right
是4
如果我们不使用初始化器,即不对Up
赋值,则Up
的值默认是0,之后的成员依旧开始递增
枚举的使用方式与对象类似:
enum Gender {
male,
female,
}
console.log(Gender.male, Gender.female); // 0 1
枚举可用作类型: 数字型枚举用作类型时能匹配任何数字,即想当于number
类型
// 对于数字型枚举,这个a变量只能保存male和female,或任何数字
let a: Gender;
a = Gender.male;
a = Gender.female;
a = 99;
// a = "Ailjx"; // err :不能将类型“"Ailjx"”分配给类型“Gender”。
每个枚举成员都有一个与之关联的值,可以是常量或计算值
在以下情况下,枚举成员被认为是常量:
没有初始化器(未初始化值)
如果是第一个成员就自动被赋值为0,如果不是第一个成员就自动被赋值为上一个值加1,无论如何都有一个确定的值,所以被认为是常量
// E1,E2,E3中的所有枚举成员都是常数
enum E1 { X, Y, Z,}
enum E2 { A = 1, B, C,}
enum E3 {D,}
枚举成员用以下常量枚举表达式进行初始化:
枚举表达式的字面意思(基本上是一个字符串字面量或一个数字字面量)
enum E1 { x = "1"}
enum E2 { x = 1 }
对先前定义的常量枚举成员的引用(可以来自不同的枚举)
enum E1 {x,}
enum E2 {x = E1.x, y = E2.x,}
一个括号内的常量枚举表达式
应用于常量枚举表达式的 +
, -
, ~
一元运算符之一
enum E { x = -9,}
+
, -
, *
, /
, %
, <<
, >>
, >>
, &
, |
, ^
以常量枚举表达式为操作数的二元运算符
enum E1 {x = 1,}
enum E2 {x = E1.x + 1,}
如果常量枚举表达式被评估为NaN
或Infinity
,这是一个编译时错误
除了常量成员之外的就都是计算成员:
let a = 1;
function fn() {
return 1;
}
enum E {
x = ++a,
y = a,
z = fn(),
A = "Ailjx".length,
}
枚举E
中的所有成员都是计算成员
数字枚举可以混合在计算成员和常量成员中,但没有初始化器的枚举成员要么需要放在第一位,要么必须在常量成员之后(因为只有在常量成员之后才会自增1):
let a = 1;
enum E {
x,
y = a,
}
这个例子中y
是计算成员,则x
只能放在y
的前边即第一位,不然会报错:
因为x
没有初始化器,若它不在第一位,它就会在上一个成员的基础上加1,但若上一个成员是计算成员,这种行为就不会被TypeScript
处理了,并会抛出错误
在一个字符串枚举中,每个成员都必须用一个字符串或另一个字符串枚举成员进行常量初始化:
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
字符串枚举没有自动递增的行为
字符串枚举用作类型时只能匹配到自身枚举成员:
enum Gender {
male = "9",
female = "Ailjx",
}
// 这个a变量只能保存male和female,数字和字符串都不行
let a: Gender;
a = Gender.male;
a = Gender.female;
// a = 9; // 不能将类型“9”分配给类型“Gender”
// a = "Ailjx"; // err :不能将类型“"Ailjx"”分配给类型“Gender”。
字符串和数字成员混合的枚举称为异构枚举,但官方并不建议这么做:
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES",
}
字面枚举成员是一个没有初始化值的常量枚举成员,或者其值被初始化为:
"foo"
, "bar"
, "baz"
)。当枚举中的所有成员都具有字面枚举值时,一些特殊的语义就会发挥作用:
枚举成员也能当作类型来用
enum E {
A = 1,
B = "Ailjx",
C = "Ailjx",
D = -1,
}
interface Author {
// E.A和E.D相当于number类型
age: E.A;
age2: E.D;
// 而E.B可不是简单的string类型,它限制了只有枚举E中值为"Ailjx"的成员才能赋值给name和name2
name: E.B;
name2: E.B;
}
let c: Author = {
age: 12, // ok
age2: 36, // ok
name: E.C, // ok
// name2的类型为E.B,并非简单的是"Ailjx"字面类型,只有枚举E中的"Ailjx"才能对其赋值
name2: "Ailjx", // err:不能将类型“"Ailjx"”分配给类型“E.B”
};
枚举类型本身有效地成为每个枚举成员的联合,使用联合枚举,类型系统能够利用它知道枚举本身中存在的确切值集的事实,正因为如此,TypeScript
可以捕获我们可能会错误地比较值的错误:
enum E {
Foo,
Bar,
}
function f(x: E) {
if (x !== E.Foo || x !== E.Bar) {
// ❌❌❌err:此条件将始终返回 "true",因为类型 "E.Foo" 和 "E.Bar" 没有重叠。
//...
}
}
枚举是在运行时存在的真实对象,例如,下面这个枚举:
enum E {
X,
Y,
Z,
}
实际上可以传递给函数:
enum E {
X,
Y,
Z,
}
function f(obj: { X: number }) {
return obj.X;
}
// 可以正常工作,因为'E'有一个名为'X'的属性,是一个数字。
f(E);
尽管枚举是运行时存在的真实对象,但keyof
关键字对枚举的工作方式与对典型对象的预期完全不同:
// 对象类型
interface A {
b: number;
c: number;
}
// type T = "b"|"c"
type T = keyof A;
let a: T = "b";
a = "c";
// 枚举类型
enum E {
X,
Y,
Z,
}
// type T = "toString" | "toFixed" | "toExponential" | "toPrecision" | "valueOf" | "toLocaleString"
type T = keyof E;
从上可以看到,我们不能使用keyof
来获取枚举类型键的字面联合类型
可以使用keyof typeof
来获得一个将枚举类型所有键表示为字符串的类型:
// 枚举类型
enum E {
X,
Y,
Z,
}
// type T = "X" | "Y" | "Z"
type T = keyof typeof E;
从这,我们反向思考能发现对枚举类型使用typeof
能够获得该枚举的对象类型:
// 枚举类型
enum E {
X,
Y,
Z,
}
// type T = { X: number, Y: number, Z: number }
type T = typeof E;
const a: T = { X: 1, Y: 2, Z: 3 };
数字枚举的成员还可以得到从枚举值到枚举名称的反向映射:
enum Enum {
A,
}
let a = Enum.A; // a为枚举值
console.log(a); // 0
let nameOfA = Enum[a]; // 根据枚举值获得枚举名称(根据键值获得键名)
console.log(nameOfA); // "A"
TypeScript
将其编译为以下 JavaScript
:
"use strict";
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
let a = Enum.A; // a为枚举值
console.log(a); // 0
let nameOfA = Enum[a]; // 根据枚举值获得枚举名称(根据键值获得键名)
console.log(nameOfA); // "A"
在此生成的代码中,枚举被编译成一个对象,该对象存储正向 ( name-> value
) 和反向 ( value-> name
) 映射,对其他枚举成员的引用始终作为属性访问发出,并且从不内联
字符串枚举成员不会被生成反向映射!
为了避免在访问枚举值时编译产生额外的生成代码和额外的间接性的代价,可以使用常量枚举,常量枚举使用枚举上的const
修饰符来定义的:
const enum E {
A,
}
常量枚举只能使用常量枚举表达式( 常量枚举不能有计算成员),并且与常规枚举不同,它们在编译期间会被完全删除:
const enum E {
A,
}
let arr = E.A;
编译后:
"use strict";
let arr = 0 /* E.A */;
普通的枚举(去掉const
修饰符)编译为:
"use strict";
var E;
(function (E) {
E[E["A"] = 0] = "A";
})(E || (E = {}));
let arr = E.A;
使用declare
来定义环境枚举,环境枚举成员初始化表达式必须是常数表达式(不能有计算成员):
在现代TypeScript
中,一般不需要使用枚举,因为一个对象的常量就足够了
const enum EDirection {
Up,
Down,
Left,
Right,
}
// (enum member) EDirection.Up = 0
EDirection.Up;
// 将枚举作为一个参数
// dir的类似于number类似
function walk(dir: EDirection) {}
walk(EDirection.Left);
walk(99) // ok
将上述代码改写成对象形式:
const ODirection = {
Up: 0,
Down: 1,
Left: 2,
Right: 3,
} as const;
// (property) Up: 0
ODirection.Up;
// 相比使用枚举,需要一个额外的行来推算出类型
type Direction = typeof ODirection[keyof typeof ODirection];
function run(dir: Direction) {} // type dir = 0 | 1 | 2 | 3
run(ODirection.Right);
run(99); // err:类型“99”的参数不能赋给类型“Direction”的参数
可以看到使用对象改写枚举反而会更安全,类型限制更准确
至此,TypeScript
枚举的内容就全部结束了,关注博主下篇更精彩!
博主的TypeScript从入门到精通专栏正在慢慢的补充之中,赶快关注订阅,与博主一起进步吧!期待你的三连支持。
参考资料:TypeScript官网
如果本篇文章对你有所帮助,还请客官一件四连!❤️