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 相关文章推荐
jQuery获取地址栏参数插件(模仿C#)
Oct 26 Javascript
js 如何实现对数据库的增删改查
Nov 23 Javascript
JavaScript包装对象使用介绍
Aug 29 Javascript
JavaScript中创建类/对象的几种方法总结
Nov 29 Javascript
javascript为下拉列表动态添加数据项
May 23 Javascript
jQuery实现伸展与合拢panel的方法
Apr 30 Javascript
js实现仿阿里巴巴城市选择框效果实例
Jun 24 Javascript
Js实现简单的小球运动特效
Feb 18 Javascript
Bootstrap基本插件学习笔记之轮播幻灯片(23)
Dec 08 Javascript
基于vue2.x的电商图片放大镜插件的使用
Jan 22 Javascript
JavaScript数组方法的错误使用例子
Sep 13 Javascript
解决layui调用自定义方法提示未定义的问题
Sep 14 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.MVC的模板标签系统(五)
2006/09/05 PHP
PHP高级编程实例:编写守护进程
2014/09/02 PHP
ThinkPHP 模板引擎使用详解
2017/05/07 PHP
thinkphp中U方法按路由规则生成url的方法
2018/03/12 PHP
PHP与Perl之间知识点区别整理
2019/03/19 PHP
JavaScript 编程引入命名空间的方法与代码
2007/08/13 Javascript
保证JavaScript和Asp、Php等后端程序间传值编码统一
2009/04/17 Javascript
js实现的真正的iframe高度自适应(兼容IE,FF,Opera)
2010/03/07 Javascript
jQuery EasyUI API 中文文档 - Form表单
2011/10/06 Javascript
javascript的动态加载、缓存、更新以及复用(一)
2014/06/09 Javascript
BootStrap组件之进度条的基本用法
2017/01/19 Javascript
微信小程序 ecshop地址三级联动实现实例代码
2017/02/28 Javascript
vue深入解析之render function code详解
2017/07/18 Javascript
jquery+css实现简单的图片轮播效果
2017/08/07 jQuery
vue移动端下拉刷新和上滑加载
2020/10/27 Javascript
[10:49]2014国际邀请赛 叨叨刀塔第二期为真正的电竞喝彩
2014/07/21 DOTA
[56:21]LGD vs IG 2018国际邀请赛小组赛BO2 第二场 8.18
2018/08/19 DOTA
python开发中range()函数用法实例分析
2015/11/12 Python
Python实现带百分比的进度条
2016/06/28 Python
Python模拟登陆实现代码
2017/06/14 Python
Python用户推荐系统曼哈顿算法实现完整代码
2017/12/01 Python
获取python的list中含有重复值的index方法
2018/06/27 Python
浅述python中深浅拷贝原理
2018/09/18 Python
打包python 加icon 去掉cmd黑窗口方法
2019/06/24 Python
django 快速启动数据库客户端程序的方法示例
2019/08/16 Python
python中下标和切片的使用方法解析
2019/08/27 Python
Python使用文件操作实现一个XX信息管理系统的示例
2020/07/02 Python
纯CSS3实现的井字棋游戏
2020/11/25 HTML / CSS
HTML5触摸事件演化tap事件介绍
2016/03/25 HTML / CSS
联想德国官网:Lenovo Germany
2018/07/04 全球购物
三星英国官网:Samsung英国
2018/09/25 全球购物
高三语文教学反思
2014/01/15 职场文书
大学毕业自我评价
2014/02/02 职场文书
四年级评语大全
2014/04/21 职场文书
幼儿园秋季开学寄语
2014/08/02 职场文书
党员年终个人总结
2015/02/14 职场文书