本文深入解析TypeScript枚举类型,对比数字枚举与字符串枚举的特性差异,重点讲解反向映射机制及const enum的编译时内联优化。通过实际代码示例,说明何时使用普通enum与const enum,并介绍外部枚举declare enum在类型声明中的应用,帮助开发者写出更优雅高效的TypeScript代码。
TypeScript枚举类型详解:数字与字符串枚举、反向映射及const优化实战
前言
前端开发者或许会疑惑:JavaScript诞生至今并未内置枚举类型,我们依然开发得井井有条,为何TypeScript要引入枚举?
后端开发者可能好奇:TypeScript的枚举是否与Java或.NET中的枚举完全一致? 接下来,我们将逐一探讨并解答这些疑问。
前端对枚举的需求
前端对枚举的需求
可以肯定的是,许多前端开发者会回答:我们从未使用过枚举。这是因为尽管ECMAScript将enum列为保留字,但直到ES2020仍未出台枚举的实现规范。语言层面缺乏规范和实现,并不意味着充满创造力的程序员不会自行构建枚举。 如果语言本身不提供,为何还要坚持自行实现?枚举究竟能解决什么核心问题?
枚举的实际价值
从字面理解,枚举旨在遍历一个包含有限个值的集合的所有成员。其核心要点在于:
- 有限的集合;
- 遍历操作。
换言之,只要我们需要表示某个变量的值必须属于某个有限集合的成员,枚举便是无法绕过的工具。
JavaScript版本的枚举实现
JavaScript版本的枚举实现
以下是一个满足大部分业务需求的枚举实现示例:
class Color {
// tricky:自增枚举成员值
static counter = null
// 枚举成员
static Red = new Color('Red')
static Green = new Color('Green')
// 反向映射
static valueOf(value) {
for (var name in Color) {
if (!(name in Color.prototype) && Color[name].value === value) {
return Color[name]
}
}
}
constructor(name, value){
if ('counter' in Color);else return
this.name = name
if (value == null) {
if (Color.counter === null) {
this.value = Color.counter = 0
}
else {
this.value = ++Color.counter
}
}
else {
this.value = Color.counter = value
}
}
toString() {
return `Color.${this.name}`
}
}
delete Color.counter
Object.freeze(Color) // tricky:禁止在定义之外的位置修改枚举成员
我们仅希望表达某些变量以包含Red和Green两个成员的Color有限集合作为值域,却不得不编写大量与语义无关的代码(严格遵循“能写hi绝不写hello”的原则)。在常规规模的项目中,往往存在多个枚举类型,虽然复制粘贴可以解决问题,但代码显得不够优雅。
TypeScript内置的枚举语言实现恰好解决了这一痛点。
TypeScript枚举与后端枚举的差异
TypeScript枚举与后端枚举的差异
后端开发者对枚举并不陌生(Python或Node.js后端开发者除外)。尽管TypeScript是JavaScript的超集,但最终需编译为JavaScript代码,并需兼容现有的JavaScript库,因此TypeScript的枚举无法与后端枚举完全一致。 建议大家保持空杯心态,重新理解TypeScript的枚举类型,将过往知识作为助燃剂,而非限制思维的围栏。
数字枚举与字符串枚举
数字枚举与字符串枚举
TypeScript官方教程已对枚举类型进行了详细讲解,我认为核心在于清晰理解其两大分类:
数字枚举类型
enum Response {
No = 0, // 手动设置初始化器
Yes = // 附加默认的支持自动增长的初始化器,因此Yes的值为1
}
其特性包括: 1.1. 枚举成员附带默认初始化器; 1.2. 初始化器支持自动增长; 1.3. 支持反向映射。(注意:此处指反向映射,而非通过值转换为枚举成员)
字符串枚举类型
enum Color {
Red = 'Red',
Green = 'Green',
}
其特性包括: 1.1. 必须为枚举成员设置初始化器; 1.2. 初始化器不支持自动增长; 1.3. 不支持反向映射。
计算成员和常量成员仅是上述两种枚举类型中初始化器的细分特性。
enum使数字枚举的反向映射成为可能
enum使数字枚举的反向映射成为可能
上一节提到数字枚举支持反向映射,但前提是通过enum定义的数字枚举类型才具备此功能。这是因为enum Response {No, Yes}最终会被编译为如下JavaScript代码:
var Response;
(function (Response) {
Response[Response["No"] = 0] = "No";
Response[Response["Yes"] = 1] = "Yes";
})(Response || (Response = {}));
此时,我们可以通过Response[0]反向映射得到"No"。 然而,字符串枚举类型并不具备这一特性,让我们看看enum Color {Red='Red', Green='Green'}编译后的代码:
var Color;
(function (Color) {
Color["Red"] = "Red";
Color["Green"] = "Green";
})(Color || (Color = {}));
编译结果非常朴实,无特殊处理,使用时注意差异即可。
const enum的高效编译时内联
const enum的高效编译时内联
官方文档明确指出:“大多数情况下,枚举是有效的方案。然而在某些严格场景下,为避免额外生成代码的开销及对枚举成员的非直接访问,可使用const枚举。”原因何在? 通过const enum定义的编译时枚举类型,效果与C/C++中的#define定义常量无实质区别。简言之,如果仅通过const enum定义了枚举类型且无其他调用,该代码将在编译时被直接移除。 当其他位置调用该枚举类型时,编译器会将枚举成员的值直接内联至使用处,如下所示:
const enum Response {
No,
Yes,
}
console.log(Response.NO, Response.Yes)
编译后变为console.log(0, 1),运行效果自然优于普通enum定义。
何时使用enum?何时使用const enum?
何时使用enum?何时使用const enum?
先给出结论:
使用enum的场景: 1.1. 需要使用反向映射时; 1.2. 需要编译后的JavaScript代码保留对象.属性或对象[属性]形式时。
使用const enum的场景:能不用enum时就用const enum(哈哈!) 使用enum场景中的第一条易于理解,但第二条是何意?此处提供一个真实示例,帮助大家更好理解: 背景:为Photoshop的ExtendScript编写类型声明。 需求:DialogModes.NO在ExtendScript中返回值为DialogModes.No本身,编译后的JavaScript中必须保留DialogModes.NO的代码形式。
那么,为何鼓励大家能用const enum时就用const enum呢? 这是TypeScript特意提供的编译时优化方式,如此优势为何不用?编译时优化难道不香吗?
外部枚举declare enum的作用?
外部枚举declare enum的作用?
所谓外部枚举,是指为了在TypeScript开发环境中更好地使用某些已采用JavaScript编写的库,而被迫为其编写的枚举类型声明。 例如,ExtendScript标准库存在枚举DialogModes.NO、DialogModes.YES、DialogModes.ALL。于是在.d.ts文件中编写如下外部枚举类型声明:
declare enum DialogModes {
NO,
YES,
ALL,
}
总结
总结
在日常开发中,枚举类型不可或缺。TypeScript提供了语言实现和编译时优化,不仅保护了开发者无需为如何优化实现枚举类型而日夜思考导致发际线后移,还大幅降低了因复制粘贴导致的代码库体积激增风险。
到此,这篇关于TypeScript枚举类型的超实用手册介绍完毕。如需了解更多TypeScript枚举相关内容,请搜索本站历史文章或继续浏览相关文章,希望大家日后多多支持本站!