TypeScript魔法堂:枚举的超实用手册
前言
也许前端的同学会问JavaScript从诞生至今都没有枚举类型,我们不是都活得挺好的吗?为什么TypeScript需要引入枚举类型呢?
也许被迫写前端的后端同学会问,TypeScript的枚举类型是和Java/.NET的一样吗?
下面我们来一起探讨和尝试解答吧!
前端一直都需要枚举
我敢保证,前端的同学都会万分肯定地告诉大家:我们从来没有写过枚举。那是因为虽然ECMAScript将enum
作为保留字,但至ES2020为止还没有提出枚举的实现规范。语言没有提供规范和语言实现,不代表思想活跃勇于造轮子的程序员们不会自己撸一个。
如果语言没有提供,还有那么毅然决然要自己造一个,那枚举到底能解决我们什么问题呢?
枚举真的有点用
首先,枚举字面上的意思就遍历一个存在若干个的值有穷集合的所有成员。核心有两点:
- 有穷集合;
- 遍历。
也就是说,只要我们需要表示某个变量的值必须为某个有穷集合的成员时,我们是怎么也绕不开枚举的。
写个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的枚举和后端的真不一样
后端的同学对枚举绝对是不会陌生的(除非是Pyton/Nodejs后端的同学啦),虽然TypeScript是JavaScript的超集,但最终需要编译为JavaScript代码,并且要兼容现有JavaScript库,所以确实无法和后端的枚举类型一模一样。
所以我还是建议大家运用空杯心理,重头理解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 Respose {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枚举”,那是为什么呢?
那是因为通过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
的场景:
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
的作用?
所谓外部枚举,即使我们为了在TypeScript开发环境下,更好地使用某些已采用JavaScript编写的库,而被迫为其编写的枚举类型声明。
如ExtendScript标准库存在枚举DialogModes.NO
,DialogModes.YES
,DialogModes.ALL
。于是在.d.ts
文件中编写如下外部枚举类型声明
declare enum DialogModes {
NO,
YES,
ALL,
}
总结
对于日常开发中我们绕不过枚举类型,TypeScript为我们提供语言实现和编译时优化,除了保护了我们为如何优化实现枚举类型而日思夜想导致日渐稀疏的头发外,还大大降低了因复制粘贴带来的代码库体积徒增的风险。
写更少的代码,做正确的事,早点下班岂不更快哉_
转载请注明来自:https://www.cnblogs.com/fsjohnhuang/p/13893641.html —— _肥仔John
TypeScript魔法堂:枚举的超实用手册的更多相关文章
- TypeScript魔法堂:函数类型声明其实很复杂
前言 江湖有传"动态类型一时爽,代码重构火葬场",由于动态类型语言在开发时不受数据类型的约束,因此非常适合在项目原型阶段和初期进行快速迭代开发使用,这意味着项目未来将通过重写而非重 ...
- JS魔法堂:不完全国际化&本地化手册 之 理論篇
前言 最近加入到新项目组负责前端技术预研和选型,其中涉及到一个熟悉又陌生的需求--国际化&本地化.熟悉的是之前的项目也玩过,陌生的是之前的实现仅仅停留在"有"的阶段而已. ...
- JS魔法堂:不完全国际化&本地化手册 之 实战篇
前言 最近加入到新项目组负责前端技术预研和选型,其中涉及到一个熟悉又陌生的需求--国际化&本地化.熟悉的是之前的项目也玩过,陌生的是之前的实现仅仅停留在"有"的阶段而已. ...
- HTML5魔法堂:全面理解Drag & Drop API
一.前言 在HTML4的时代,各前端工程师为了实现拖拽功能可说是煞费苦心,初听HTML5的DnD API觉得那些痛苦的日子将一去不复返,但事实又是怎样的呢?下面我们一起来看看DnD API的真面 ...
- WebComponent魔法堂:深究Custom Element 之 标准构建
前言 通过<WebComponent魔法堂:深究Custom Element 之 面向痛点编程>,我们明白到其实Custom Element并不是什么新东西,我们甚至可以在IE5.5上定 ...
- WebComponent魔法堂:深究Custom Element 之 面向痛点编程
前言 最近加入到新项目组负责前端技术预研和选型,一直偏向于以Polymer为代表的WebComponent技术线,于是查阅各类资料想说服老大向这方面靠,最后得到的结果是:"资料99%是英语 ...
- CSS魔法堂:Box-Shadow没那么简单啦:)
前言 说起box-shadow那第一个想法当然就是用来实现阴影,其实它还能用于实现其他好玩的效果的,本篇就打算说说box-shadow的那些事. 二话不说看效果 3D小球 <style typ ...
- CSS魔法堂:重拾Border之——更广阔的遐想
前言 当CSS3推出border-radius属性时我们是那么欣喜若狂啊,一想到终于不用再添加额外元素来模拟圆角了,但发现border-radius还分水平半径和垂直半径,然后又发现border-t ...
- CSS魔法堂:重拾Border之——不仅仅是圆角
前言 当CSS3推出border-radius属性时我们是那么欣喜若狂啊,一想到终于不用再添加额外元素来模拟圆角了,但发现border-radius还分水平半径和垂直半径,然后又发现border-t ...
随机推荐
- 关于SpringBoot的一点笔记
@SpringBootApplication /** * @SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用 */ @SpringBootAppl ...
- 017 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 11 变量综合案例
017 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 11 变量综合案例 本文知识点:变量 相同类型的变量可以一次同时定义多个 例:可以一行代码同时定义2个变量x ...
- C++对话框创建及修改对话框属性
转载:http://www.51testing.com/html/48/n-3151648.html 创建对话框 C++中对话框分为模式对话框和非模式对话框. 模式对话框的创建: MyDialog m ...
- Matlab中image、imagesc和imshow函数用法解析
来源:https://blog.csdn.net/zhuiyuanzhongjia/article/details/79621813 1.显示RGB图像 相同点:这三个函数都是把m*n*3的矩阵中的数 ...
- CF149D Coloring Brackets
CF149D Coloring Brackets Link 题面: 给出一个配对的括号序列(如"\((())()\)"."\(()\)"等, "\() ...
- 部署docker swarm集群
基础环境 机器名称 IP地址 安装的软件 node-1 192.168.10.190 docker-ce node-2 192.168.10.191 docker-ce node-3 192.168. ...
- 算法进阶 (LIS变形) 固定长度截取求最长不下降子序列【动态规划】【树状数组】
先学习下LIS最长上升子序列 看了大佬的文章OTZ:最长上升子序列 (LIS) 详解+例题模板 (全),其中包含普通O(n)算法*和以LIS长度及末尾元素成立数组的普通O(nlogn)算法,当然还 ...
- 这类注解都不知道,还好意思说会Spring Boot ?
前言 不知道大家在使用Spring Boot开发的日常中有没有用过@Conditionalxxx注解,比如@ConditionalOnMissingBean.相信看过Spring Boot源码的朋友一 ...
- 用flannel实现跨主机container通信
最近在看kubernetes,看到了网络部分,这部分是集群设计的难点,也是我比较感兴趣的部分.书上提到不同node的container之间通信主要使用flannel,openvswitch等技术,这些 ...
- 慕课网go语言体系课抢先体验
慕课网go语言体系课抢先体验,课程分四个阶段: <第一阶段go语言基础语法篇>,从go语言基础语法篇讲起,go语言环境集成,常用开发工具集成,常用数据类型讲解,流程控制,函数,结构体,方法 ...