TypeScript魔法堂之枚举的超实用手册


Posted in Javascript onOctober 29, 2020

前言

也许前端的同学会问JavaScript从诞生至今都没有枚举类型,我们不是都活得挺好的吗?为什么TypeScript需要引入枚举类型呢?

也许被迫写前端的后端同学会问,TypeScript的枚举类型是和Java/.NET的一样吗?
下面我们来一起探讨和尝试解答吧!

前端一直都需要枚举

我敢保证,前端的同学都会万分肯定地告诉大家:我们从来没有写过枚举。那是因为虽然ECMAScript将enum作为保留字,但至ES2020为止还没有提出枚举的实现规范。语言没有提供规范和语言实现,不代表思想活跃勇于造轮子的程序员们不会自己撸一个。
如果语言没有提供,还有那么毅然决然要自己造一个,那枚举到底能解决我们什么问题呢?

枚举真的有点用

首先,枚举字面上的意思就遍历一个存在若干个的值有穷集合的所有成员。核心有两点:

  1. 有穷集合;
  2. 遍历。

也就是说,只要我们需要表示某个变量的值必须为某个有穷集合的成员时,我们是怎么也绕不开枚举的。

写个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为我们提供语言实现和编译时优化,除了保护了我们为如何优化实现枚举类型而日思夜想导致日渐稀疏的头发外,还大大降低了因复制粘贴带来的代码库体积徒增的风险。

到此这篇关于TypeScript魔法堂之枚举的超实用手册的文章就介绍到这了,更多相关TypeScript 枚举内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
Js之软键盘实现(js源码)
Jan 30 Javascript
js word表格动态添加代码
Jun 07 Javascript
javascript new后的constructor属性
Aug 05 Javascript
jquery获取div宽度的实现思路与代码
Jan 13 Javascript
javascript动态的改变IFrame的高度实现自动伸展
Oct 12 Javascript
javascript获取元素CSS样式代码示例
Nov 28 Javascript
JS将制定内容复制到剪切板示例代码
Feb 11 Javascript
轻量级网页遮罩层jQuery插件用法实例
Jul 31 Javascript
jQuery模拟原生态App上拉刷新下拉加载更多页面及原理
Aug 10 Javascript
浅析BootStrap模态框的使用(经典)
Apr 29 Javascript
jQuery旋转插件jqueryrotate用法详解
Oct 13 Javascript
JavaScript原型链与继承操作实例总结
Aug 24 Javascript
解决antd的Form组件setFieldsValue的警告问题
Oct 29 #Javascript
vue 函数调用加括号与不加括号的区别
Oct 29 #Javascript
JavaScript实现随机点名小程序
Oct 29 #Javascript
在antd中setFieldsValue和defaultVal的用法
Oct 29 #Javascript
微信小程序淘宝首页双排图片布局排版代码(推荐)
Oct 29 #Javascript
解决antd datepicker 获取时间默认少8个小时的问题
Oct 29 #Javascript
Antd中单个DatePicker限定时间输入范围操作
Oct 29 #Javascript
You might like
PHP支持多种格式图片上传(支持jpg、png、gif)
2011/11/03 PHP
PHP ? EasyUI DataGrid 资料取的方式介绍
2012/11/07 PHP
php 输出缓冲 Output Control用法实例详解
2020/03/03 PHP
DHTML 中的绝对定位
2006/11/26 Javascript
Jquery选择器 $实现原理
2009/12/02 Javascript
网页图片延时加载的js代码
2010/04/22 Javascript
深入讲解AngularJS中的自定义指令的使用
2015/06/18 Javascript
javascript Promise简单学习使用方法小结
2016/05/17 Javascript
jQuery中Ajax全局事件引用方式及各个事件(全局/局部)执行顺序
2016/06/02 Javascript
JS实现页面载入时随机显示图片效果
2016/09/07 Javascript
Bootstrap 模态框实例插件案例分析
2016/12/28 Javascript
Vue自定义指令拖拽功能示例
2017/02/17 Javascript
搭建简单的nodejs http服务器详解
2017/03/09 NodeJs
nodejs模块nodemailer基本使用-邮件发送示例(支持附件)
2017/03/28 NodeJs
解决canvas画布使用fillRect()时高度出现双倍效果的问题
2017/08/03 Javascript
完美解决mui框架off-canvas侧滑超出部分隐藏无法滚动的问题
2018/01/25 Javascript
jQuery实现模糊搜索功能的方法分析
2018/06/29 jQuery
angular4自定义表单控件[(ngModel)]的实现
2018/11/23 Javascript
JS散列表碰撞处理、开链法、HashTable散列示例
2019/02/08 Javascript
js打开word文档预览操作示例【不是下载】
2019/05/23 Javascript
JavaScript 面向对象基础简单示例
2019/10/02 Javascript
Python xlrd读取excel日期类型的2种方法
2015/04/28 Python
python 遍历列表提取下标和值的实例
2018/12/25 Python
详解用Python练习画个美队盾牌
2019/03/23 Python
Django实现简单网页弹出警告代码
2019/11/15 Python
Python TCP通信客户端服务端代码实例
2019/11/21 Python
python wav模块获取采样率 采样点声道量化位数(实例代码)
2020/01/22 Python
学生周末长期请假条
2014/02/15 职场文书
汽车专业求职信
2014/06/05 职场文书
乡镇党的群众路线教育实践活动个人对照检查材料
2014/09/23 职场文书
公司股份合作协议书
2014/12/07 职场文书
初中运动会前导词
2015/07/20 职场文书
2015小学师德工作总结
2015/07/21 职场文书
用Python将GIF动图分解成多张静态图片
2021/06/11 Python
Python 游戏大作炫酷机甲闯关游戏爆肝数千行代码实现案例进阶
2021/10/16 Python
Redis 报错 error:NOAUTH Authentication required
2022/05/15 Redis