前言

TypeScript是什么?

引用官方原话



TypeScript是JavaScript类型的超集,它可以编译成纯JavaScript。TypeScript可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的。



个人的理解:

  • 由微软开发的一个开源项目,对javascript的一个扩展
  • 弥补了许多javascript的不足(比如由于javascript弱类型原因,许多错误只能在运行时知道;)
  • 对类型进行了强化(这个颇具争议,有人认为这破坏了javascript的动态性,有人认为加强规范了javascript,我个人更倾向于后者,毕竟在项目中,太动态的东西会变得难以理解和维护且容易出错)
  • 扩展了许多面向对象的用法
  • 当下很火的一个技术(GitHub6.2k的start,8.3k的fork足见其火热程度)
  • 本质上还是Javascript

为什么选择TypeScript

TypeScript具有许多优点:

  • 多端可用,完美契合前端技术栈(web端Vue项目、微信小程序、uniapp均可使用)
  • 兼容性高
  • 支持ES新特性,包括还在提案中的特性
  • 比较新的特性编译成ES5代码
  • 友好的代码提示
  • 具有文档特性,许多方法看类型定义就知道如何使用
  • 增强了编辑器的代码提示功能
  • 强类型约束,编译前尽可能减少错误和bug
  • 编辑阶段和编译阶段会报错,但不影响编译结果
  • 更多面向对象编程的特性
  • 易于重构
  • 易于团队合作
  • 社区活跃,大部分项目都有TypeScript的声明文件
  • 越来越多的库用TypeScript编写(明年发布的Vue3.0位于其中之一),是未来的趋势

准备工作

前面讲了一大堆,那么,该如何上手这样一门好用又热门的技术呢





typescript 能干什么 typescript可以开发什么_typescript 能干什么


安装

全局安装TypeScript:npm install -g typescript

安装完成之后即在编辑器中编写代码(无需额外配置,热门编辑器对TypeScript支持良好)

开始第一个TypeScript程序——Hello word

在webStrom(为什么是webstrom?以后会统一使用webstrom开发前端,而且webstrom对TypeScript支持友好,能够编辑器中自动编译TypeScript文件,且能够在编辑器内运行编译后的文件)中新建一个hellowTypeScript.ts文件,输入:


console.log('hellow TypeScript')


命令行输入:tsc hellowTypeScript.ts

编译之后会得到hellowTypeScript.js文件,使用node运行或者引入页面仔浏览器运行即可

基础数据类型

布尔类型(boolean)

布尔类型很简单,只需要在其定义后面添加类型然后赋值即可:


let isClose:boolean=true


或者调用Boolean方法:


let isClose:boolean= Boolean(true)


其允许值只有两种:true/false,如果不是这两种,编译器就会报错:


let isClose:boolean=2


报错如下:

Type '2' is not assignable to type 'boolean'.

需要注意的是,使用Boolean构造函数返回的是Boolean对象,所以不能赋值给boolean类型的数据


let isClose:boolean=new Boolean(true)//Type 'Boolean' is not assignable to type 'boolean'.


数值类型(number)

数值类型用number来定义:


let num: number = 6
let num2: number = Number(2)//调用number
let num3: number = new Number(2)//调用构造函数,报错:Type 'Number' is not assignable to type 'number'.
let hexNum: number = 0xf00d//十六进制
let binaryNum: number = 0b1010 //二进制表示法
let octalNum: number = 0o744//八进制表示法
let notANumber: number = NaN
let infinityNumber: number = Infinity


字符串类型(string)

使用string定义字符:


let firendNum:number=20
let myName:string='jepson'
let lastName:string=String('smith')
let addres:string=new String('smith')//报错:Type 'String' is not assignable to type 'string'.
//等价于lastName+myName+' has '+firendNum+' friends'
let message=`${myName}·${lastName} has ${firendNum} friends`


null、undefined

在TypeScript中null、undefined是所有类型的子类型,所有其他类型都能够被赋值为null和undefined:


let u:undefined=undefined
let n:null=null
let num:number=undefined
let str:string=null


你可能会问,为什么在Vue项目中不能这么用?

因为Vue项目中默认配置了--strictNullChecks标记,这样也能避免许多错误,所以如果想要能够赋值null或者undefined,最好显式声明:


let name:string|null|undefined=null


symbol(永远唯一的值)

Symbol是ES6的新原生类型,表示永远不可变且唯一的值,如果你还不了解这个类型。在TypeScript中表现为symbol


let globalKey:symbol=Symbol()
let globalKey2:symbol=Symbol()
console.log(globalKey===globalKey2)//false


空值(void)

void表示空值,只能赋值为null/undefined(所有类型的子类型),如果用于变量中这或许没什么用,但是用于函数中表示不返回任何值:


let myFirstName:void=undefined
let myLastName:void=null
function fn1():void {
  return null
}
function fn2():void {
  return undefined
}
function fn3():void {
  console.log('this is fn3')
}


any(天使还是魔鬼?)

any用于不确定其类型时所指定的类型,表示任何类型,如:第三方库、后端返回数据不确定(尽量要求后端返回的数据确定)、动态内容等:


let anyValue:any=true
anyValue=20
anyValue='anyValue'
anyValue=Symbol()
anyValue=null
anyValue=undefined
anyValue=new Error('anyvalue')
anyValue=['anyValue']


TypeScript中的any类型还曾被一个著名的开发者戏称道(具体啥名儿,咱也忘了,只记得他说的那句话):


any是TypeScript最大的Bug


注意:指定any类型之后编辑器和编译器将不会进行检查,这或许有许多便捷之处,但是从另一个角度来说,不进行检查也就代表许多有关的错误在编辑器中或者编译阶段不会报错,所以应尽量避免在项目中使用any

函数

函数在Javascript中非常重要,因为所有行为都是通过函数来执行。在《函数式编程指南》一书中指出:


函数是Javascript的一等公民


TypeScript中的函数,需要把输入和输出都考虑进去:


//函数声明式
function add(num1:number,num2:number):number {
  return num1+num2
}

//函数表达式
const reduce=(num1:number,num2:number):number=>num1-num2

console.log(add(1,1))//2
console.log(reduce(1,1))//0


函数中的类型

如上面代码,函数需要指定输入和输出的类型:

  • 少输入参数、多输入参数、参数类型不符合都会报错
  • 如果不指定输入的类型,默认为any(不推荐使用any,所以Vue项目中会报错)
  • 不指定输出类型则默认为void,表示无返回结果;如果有返回,TypeScript会根据返回数据自动推断类型


function runAway() {
  console.log('又是个黑厂,收起行李箱、席子、衣架,提桶跑路')
}


上面代码等价于下面的代码:


function runAway():void {
  console.log('又是个黑厂,收起行李箱、席子、衣架,提桶跑路')
}


下面是个自动推断类型的例子:


function add(num1:number,num2:number) {
  return num1+num2
}

//自动推断其类型
const num:number=add(1,2)


可选参数与默认参数

可选参数

对于一些参数不是必须的情况下,可以使用可选参数,可选参数使用?:定义,表示这个参数是非必须的:


/**
 * 模拟吃午饭行为
 * @param mianFood 主食
 * @param mainCourse 主菜
 * @param sideDish 副菜
 * @param soup 汤,这是个非必填参数
 * @param drinks 饮料,这是个非必填参数
 * @param fruits 水果,这是个非必填参数
 */
function haveLunch(mianFood: string, mainCourse: string,sideDish:string,soup?: string, drinks?: string, fruits?: string) {
  const foods = Object.keys(arguments).map((key: string) => arguments[key])
  console.log(`午饭时间到,开始吃午饭,今天的午饭是:${foods.join('、')}`)
}

haveLunch('米饭', '黄豆炖猪蹄', '蚝油生菜')//午饭时间到,开始吃午饭,今天的菜有:米饭、黄豆炖猪蹄、蚝油生菜


如果填入可选参数,必须按照顺序(假如填入填入饮料,则必须填入汤):


/**
 * 模拟吃午饭行为
 * @param mianFood 主食
 * @param mainCourse 主菜
 * @param sideDish 副菜
 * @param soup 汤,这是个非必填参数
 * @param drinks 饮料,这是个非必填参数
 * @param fruits 水果,这是个非必填参数
 */
function haveLunch(mainFood: string, mainCourse: string, sideDish: string, soup?: string, drinks?: string, fruits?: string) {
  const foods = [
    `主食:${mainFood ? mainFood : '无'}`,
    `主菜:${mainCourse ? mainCourse : '无'}`,
    `副菜:${sideDish ? sideDish : '无'}`,
    `汤:${soup ? soup : '无'}`,
    `饮料:${drinks ? drinks : '无'}`,
    `水果:${fruits ? fruits : '无'}`,
  ]
  console.log(`午饭时间到,开始吃午饭,今天的午饭有:\n${foods.join('\n')}`)
}

//   午饭时间到,开始吃午饭,今天的午饭有:
//   主食:米饭
//   主菜:黄豆炖猪蹄
//   副菜:蚝油生菜
//   汤:可乐(可乐本来是饮料的)
//   饮料:无
//   水果:无
haveLunch('米饭', '黄豆炖猪蹄', '蚝油生菜', '可乐')

//   午饭时间到,开始吃午饭,今天的午饭有:
//   主食:米饭
//   主菜:黄豆炖猪蹄
//   副菜:蚝油生菜
//   汤:西红柿蛋汤
//   饮料:可乐
//   水果:无
haveLunch('米饭', '黄豆炖猪蹄', '蚝油生菜', '西红柿蛋汤','可乐')


需要注意的是:可选参数后面不能带必选参数,必选参数前面只能是0或1个必选参数


/**
 * 模拟吃午饭行为
 * @param mianFood 主食
 * @param mainCourse 主菜
 * @param sideDish 副菜
 * @param soup 汤,这是个非必填参数
 * @param drinks 饮料,这是个非必填参数
 * @param fruits 水果,这是个非必填参数
 */
//报错:A required parameter cannot follow an optional parameter.
function haveLunch(mainFood: string, mainCourse: string, sideDish: string, soup?: string, drinks: string, fruits?: string) {
  const foods = [
    `主食:${mainFood ? mainFood : '无'}`,
    `主菜:${mainCourse ? mainCourse : '无'}`,
    `副菜:${sideDish ? sideDish : '无'}`,
    `汤:${soup ? soup : '无'}`,
    `饮料:${drinks ? drinks : '无'}`,
    `水果:${fruits ? fruits : '无'}`,
  ]
  console.log(`午饭时间到,开始吃午饭,今天的午饭有:\n${foods.join('\n')}`)
}


默认参数

用于为那些非必填项设置默认值:


/**
 * 模拟吃午饭行为
 * @param mianFood 主食
 * @param mainCourse 主菜
 * @param sideDish 副菜
 * @param soup 汤,默认为无,非必填参数
 * @param drinks 饮料,默认为白开水(没有饮料就喝喝白开水吧),非必填参数
 * @param fruits 水果,默认为无,非必填参数
 */
function haveLunch(mainFood: string, mainCourse: string, sideDish: string, soup: string='无', drinks: string='白开水', fruits: string='无') {
  const foods = [
    `主食:${mainFood ? mainFood : '无'}`,
    `主菜:${mainCourse ? mainCourse : '无'}`,
    `副菜:${sideDish ? sideDish : '无'}`,
    `汤:${soup ? soup : '无'}`,
    `饮料:${drinks ? drinks : '无'}`,
    `水果:${fruits ? fruits : '无'}`,
  ]
  console.log(`午饭时间到,开始吃午饭,今天的午饭有:\n${foods.join('\n')}`)
}

// 午饭时间到,开始吃午饭,今天的午饭有:
// 主食:米饭
// 主菜:黄豆炖猪蹄
// 副菜:蚝油生菜
// 汤:无
// 饮料:白开水
// 水果:无
haveLunch('米饭', '黄豆炖猪蹄', '蚝油生菜')


默认参数与可选参数不同:

  1. 默认参数没有位置限制,第一个参数也能设置默认参数,后面可以带必选参数
  2. 默认参数如果传入undefinednull不会取代默认值,而可选参数会直接复制;利用这一特性可以为每个参数设置默认值,如果是默认值可以传入undefinednull


// 午饭时间到,开始吃午饭,今天的午饭有:
// 主食:米饭
// 主菜:黄豆炖猪蹄
// 副菜:蚝油生菜
// 汤:无(传入的是undefined)
// 饮料:白开水(传入的是null)
// 水果:西瓜
haveLunch('米饭', '黄豆炖猪蹄', '蚝油生菜',undefined,null,'西瓜')


=>与箭头函数

你一定好奇,=>不是ES6箭头函数吗,这还有什么可讲的?

typescript 能干什么 typescript可以开发什么_javascript_02

在TypeScript类型定义中=>用于表示函数,左边是输入类型,右边是输出类型;

而在ES6中,=>代表的是一个箭头函数,左边参数,右边代码块

下面例子中展示了TypeScript类型定义中的和ES6的=>之间的区别


let add: (num1: number, num2: number) => number
add = (num1, num2) => num1 + num2


rest参数(剩余参数)

rest参数是ES6的新特性,还不了解的同学请看传送门:《ECMAScript入门教程》函数篇

在TypeScript中我们可以为rest参数设定类型:


function add(num1:number,num2:number,...numArr:number[]):number {
  let num=num1+num2
  numArr.forEach((n:number)=>{
    num+=n
  })
  return num
}

console.log(add(1,1,1,1,1,1))//6


interface(接口)


在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。


TypeScript中的interface(接口)非常灵活,常用与对类的部分行为抽象、规定对象的形状(哪些属性/方法)


interface User {
  username:string;
  password:string;
}

//下面这个写法会报错
const u:User={
  username: 'admin',
  password: 12345678,//报错:Type 'number' is not assignable to type 'string'.
  level:1//报错:Type '{ username: string; password: string; level: number; }' is not assignable to type 'User'.
}

//这个写法不会报错
const u2:User={
  username: 'admin',
  password: '12345678'
}


可选属性

interface规定了对象应该有那些属性,而有时候对象的某些属性并不是固定有的,这时候我们可以定义一个可选属性,即使缺少可选属性,仍然不会报错:


interface User {
  username:string;
  password:string;
  level?:number
}

const u:User={
  username: 'admin',
  password: '12345678'
}


只读属性

有这样一种应用场景:希望对象在创建的时候初始化值,而一旦初始化之后便不能改变其值。这时候只读属性就派上用场了:


interface User {
  readonly username:string;
  password:string;
  level?:number
}

const u:User={
  username: 'admin',
  password: '12345678',
  level:1
}

u.username='Admin'//报错:Cannot assign to 'username' because it is a read-only property.


函数属性

那么,如何指定对象中的函数呢?

采用functionName(...args:dataType):returnType的形式指定:


interface User {
  readonly username:string;
  password:string;
  level?:number;
  upgrade(level:number):number
}

const u:User={
  username: 'admin',
  password: '12345678',
  level:1,
  upgrade(level:number): number {
    this.level+=level
    return this.level
  }
}
console.log(u.upgrade(2))//3


动态可索引属性实现伪数组

前面说过,TypeScript的interface非常灵活,可以用动态可索引属性,所以我们可以利用这点实现伪数组类型:


interface PseudoArray{
  [index:number]:number
}

const pseudoArray={
  0:20,
  1:11,
  2:33
}


或者动态对象:


const pseudoArray = {
  0: 20,
  1: 11,
  2: 33
}

interface DynamicObject {
  [key: string]: any
}

const dynamicObject = {
  name: 'jepson',
  address: 'anywhere'
}


接口定义函数类型

可以使用接口为函数定义类型:


interface Add {
  (num1: number, num2: number): number
}

let add: Add = (num1, num2) => num1 + num2


接口间的继承

什么是继承?以下原话摘自维基百科:


继承(英语:inheritance)是面向对象软件技术当中的一个概念。如果一个类别B“继承自”另一个类别A,就把这个B称为“A的子类”,而把A称为“B的父类别”也可以称“A是B的超类”。继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类追加新的属性和方法也是常见的做法。 一般静态的面向对象编程语言,继承属于静态的,意即在子类的行为在编译期就已经决定,无法在运行期扩展。

有些编程语言支持多重继承,即一个子类可以同时有多个父类,比如C++编程语言;而在有些编程语言中,一个子类只能继承自一个父类,比如Java编程语言,这时可以透过实现接口来实现与多重继承相似的效果。

现今面向对象程序设计技巧中,继承并非以继承类别的“行为”为主,而是继承类别的“类型”,使得组件的类型一致。另外在设计模式中提到一个守则,“多用合成,少用继承”,此守则也是用来处理继承无法在运行期动态扩展行为的遗憾。


TypeScript中的继承比较灵活,能够多重继承,用extends实现:


/**
 * 抽象出动物的一些基本活动
 */
interface Animal {
  eat(food: string): void;

  run(): void;

  sleep(): void;
}

/**
 * 人类的本质也是动物,所以应该从动物继承这些基本属性
 */
interface Human extends Animal {
  name: string;
  hair: string;
  readonly birthday: Date;
  sex: 'MAN' | 'WOMAN';//此处的联合类型将会在下一篇中讲到讲到
  interest: string;
  age: number;
}

/**
 * 男性本质上是人类,所以应该继承人类的一些特性
 */
interface Man extends Human {
  age: number;//男生们对自己的年龄看的很淡
}

/**
 * 女性本质上是人类,所以应该继承人类的一些特性
 */
interface Woman extends Human {
  age: 18;//女生们都希望自己永远18岁,所以Woman可以有自己的年龄,而不是从Human里面继承,这个叫做
}

const epson: Man = {
  age: 28,
  birthday: new Date(1992,1,1),
  hair: '短发',
  interest: '打篮球 打游戏 钓鱼',
  name: 'epson',
  sex: 'MAN',
  eat(food: string): void {
    console.log(`${this.name}吃${food}`)
  },
  run(): void {
    console.log(`${this.name}开始跑步`)
  },
  sleep(): void {
    console.log(`${this.name}躺下来休息`)
  }
}

const marry: Woman = {
  age: 19,//Type '19' is not assignable to type '18'.
  birthday: new Date(1992,1,1),
  hair: '长发',
  interest: '逛街 吃美食 八卦',
  name: 'marry',
  sex: 'MAN',
  eat(food: string): void {
    console.log(`${this.name}吃${food}`)
  },
  run(): void {
    console.log(`${this.name}开始跑步`)
  },
  sleep(): void {
    console.log(`${this.name}躺下来休息`)
  }
}


数组

TypeScript定义数组类型,使得数组中不能够有除了指定类型之外的其他类型,在编辑器和编译阶段会报错(但是不影响编译)。

接下来我们看看怎么定义数组

类型+数组字面量定义


let numArr:number[]
numArr=[1,2,3]
numArr=[1,2,3,'4']//报错:Type 'string' is not assignable to type 'number'.
numArr=[1,2,3,false]//报错:Type 'false' is not assignable to type 'number'.


数组泛型定义数组

数组泛型支持定义多种类型


let numArr:Array<string|number>
numArr=[1,2,3]
numArr=[1,2,3,'4']
numArr=[1,2,3,false]//报错:Type 'false' is not assignable to type 'number'.


在实际开发中,更推荐这种方式定义数组,因为看上去更加明了、灵活,更像是一个类型

接口定义数组

前面说过,interface是个很重要的概念,它很灵活、能够应用于多种场景,除了可以用来定义伪数组,也可以用来定义真数组,其类型定义和数组泛型一样灵活:


interface NumberArray{
  [index:number]:number|string
}
let numArr:NumberArray
numArr=[1,2,3]
numArr=[1,2,3,'4']//报错:Type 'string' is not assignable to type 'number'.
numArr=[1,2,3,false]//报错:Type 'false' is not assignable to type 'number'.


元组

对于指定元素数量和类型的数组,推荐使用元组定义:


const position: [number, number] = [114.118702, 22.647837]//定义一个经纬度位置信息
let roleArr: [string, string, string]
roleArr= ['superAdmin', 'admin', 'user']//定义一个角色数组,只有超级管理员、普通管理员、用户三种角色
roleArr= ['superAdmin', 'admin', 'user','tourist']//报错:Type '[string, string, string, string]' is not assignable to type '[string, string, string]'.、

let data: [string,number,boolean]
data= ['superAdmin',0,false]//定义一个角色数组,只有超级管理员、普通管理员、用户三种角色
console.log(data[0].toString())
console.log(data[1].subString(0,1))//访问元素将会自动获得其类型,所以这里将会报错:Property 'subString' does not exist on type 'number'.
console.log(data[2])
console.log(data[4].toString())//访问越界元素将会报错: Object is possibly 'undefined'.


枚举

以下摘自维基百科:


在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。

枚举是一个被命名的整型常数的集合,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。


枚举项有两种类型:常数项(constant member)和计算所得项(computed member)。


当满足以下条件时,枚举成员被当作是常数:

  • 不具有初始化函数并且之前的枚举成员是常数。在这种情况下,当前枚举成员的值为上一个枚举成员的值加 1。但第一个枚举元素是个例外。如果它没有初始化方法,那么它的初始值为 0。
  • 枚举成员使用常数枚举表达式初始化。常数枚举表达式是 TypeScript 表达式的子集,它可以在编译阶段求值。当一个表达式满足下面条件之一时,它就是一个常数枚举表达式:
  • 数字字面量
  • 引用之前定义的常数枚举成员(可以是在不同的枚举类型中定义的)如果这个成员是在同一个枚举类型中定义的,可以使用非限定名来引用
  • 带括号的常数枚举表达式
  • +, -, ~ 一元运算符应用于常数枚举表达式
  • +, -, *, /, %, <<, >>, >>>, &, |, ^ 二元运算符,常数枚举表达式做为其一个操作对象。若常数枚举表达式求值后为 NaN 或 Infinity,则会在编译阶段报错


TypeScript的枚举(enum)非常灵活,默认自动赋值,比如定义一周的天数:


enum Week {
  Monday,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday,
  Sunday
}


其编译之后的结果:


var Week;
(function (Week) {
    Week[Week["Monday"] = 0] = "Monday";
    Week[Week["Tuesday"] = 1] = "Tuesday";
    Week[Week["Wednesday"] = 2] = "Wednesday";
    Week[Week["Thursday"] = 3] = "Thursday";
    Week[Week["Friday"] = 4] = "Friday";
    Week[Week["Saturday"] = 5] = "Saturday";
    Week[Week["Sunday"] = 6] = "Sunday";
})(Week || (Week = {}));
//# sourceMappingURL=doc1.js.map


手动赋值

值为数字

当值为数字时,会根据手动赋值逐个递增:


enum Week {
  Monday=1,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday,
  Sunday
}


编译结果为:


var Week;
(function (Week) {
    Week[Week["Monday"] = 1] = "Monday";
    Week[Week["Tuesday"] = 2] = "Tuesday";
    Week[Week["Wednesday"] = 3] = "Wednesday";
    Week[Week["Thursday"] = 4] = "Thursday";
    Week[Week["Friday"] = 5] = "Friday";
    Week[Week["Saturday"] = 6] = "Saturday";
    Week[Week["Sunday"] = 7] = "Sunday";
})(Week || (Week = {}));
//# sourceMappingURL=doc1.js.map


需要注意的是:如果值有重复项,TypeScript是不会发觉的,所以应该避免此类型情况:


//TypeScript
enum Week {
  Monday=1,
  Tuesday,
  Wednesday=1,
  Thursday,
  Friday,
  Saturday,
  Sunday
}
//编译后
var Week;
(function (Week) {
    Week[Week["Monday"] = 1] = "Monday";
    Week[Week["Tuesday"] = 2] = "Tuesday";
    Week[Week["Wednesday"] = 1] = "Wednesday";
    Week[Week["Thursday"] = 2] = "Thursday";
    Week[Week["Friday"] = 3] = "Friday";
    Week[Week["Saturday"] = 4] = "Saturday";
    Week[Week["Sunday"] = 5] = "Sunday";
})(Week || (Week = {}));
//# sourceMappingURL=doc1.js.map


值为字符串

赋值为字符串之后,后面的都需要手动赋值(不像数字那样会自动递增),当某个值再次赋值为数字才会自动递增:


//TypeScript
enum Week {
  Monday=1,
  Tuesday='2',
  Wednesday=3,
  Thursday,
  Friday,
  Saturday,
  Sunday
}
//编辑结果
var Week;
(function (Week) {
    Week[Week["Monday"] = 1] = "Monday";
    Week["Tuesday"] = "2";
    Week[Week["Wednesday"] = 3] = "Wednesday";
    Week[Week["Thursday"] = 4] = "Thursday";
    Week[Week["Friday"] = 5] = "Friday";
    Week[Week["Saturday"] = 6] = "Saturday";
    Week[Week["Sunday"] = 7] = "Sunday";
})(Week || (Week = {}));
//# sourceMappingURL=doc1.js.map


计算所得项

枚举项中的计算所得项:


enum Color {
  Red,
  Green,
  Blue = 'blue'.length
}


上面示例不会报错,但是如果计算所得项后面还有未手动赋值项,则会因为无法获取初始值而报错:


enum Color {
  Red,
  Green,
  Blue = 'blue'.length,
  Yellow//Enum member must have initializer.
}


所以,计算所得项后面必须手动赋值或者放在枚举最后:


enum Color {
  Red,
  Green,
  Blue = 'blue'.length,
  Yellow=3
}


需要注意的是,在有字符串常量的枚举中,不允许有计算所得项:


enum Color {
  Red,
  Green,
  Blue = 'blue'.length,//Computed values are not permitted in an enum with string valued members.
  Yellow='3'
}


常数枚举

常数枚举与普通枚举只相差一个const,其区别是:在编译阶段会被删除,且不能包含计算所得项:


//TypeScript
//定义一个五行枚举
const enum FiveElements {
  metal = '金',
  wood = '木',
  water = '水',
  fire = '火',
  earth = '土'
}
console.log(`五行相生:
${FiveElements.wood}生${FiveElements.fire}
${FiveElements.fire}生${FiveElements.earth}
${FiveElements.earth}生${FiveElements.metal}
${FiveElements.metal}生${FiveElements.water}
${FiveElements.water}生${FiveElements.wood}`)

console.log(`五行相剋:
${FiveElements.wood}生${FiveElements.earth}
${FiveElements.earth}生${FiveElements.water}
${FiveElements.water}生${FiveElements.fire}
${FiveElements.fire}生${FiveElements.metal}
${FiveElements.metal}生${FiveElements.wood}`)

//编译结果
console.log(`五行相生:
${"\u6728" /* wood */}生${"\u706B" /* fire */}
${"\u706B" /* fire */}生${"\u571F" /* earth */}
${"\u571F" /* earth */}生${"\u91D1" /* metal */}
${"\u91D1" /* metal */}生${"\u6C34" /* water */}
${"\u6C34" /* water */}生${"\u6728" /* wood */}`);
console.log(`五行相剋:
${"\u6728" /* wood */}生${"\u571F" /* earth */}
${"\u571F" /* earth */}生${"\u6C34" /* water */}
${"\u6C34" /* water */}生${"\u706B" /* fire */}
${"\u706B" /* fire */}生${"\u91D1" /* metal */}
${"\u91D1" /* metal */}生${"\u6728" /* wood */}`);
//# sourceMappingURL=doc1.js.map


外部枚举

外部枚举指的是使用declare定义的枚举,他能够在外部被引用:


//enum.ts
declare enum FiveElements {
  metal = '金',
  wood = '木',
  water = '水',
  fire = '火',
  earth = '土'
}
//main.ts
console.log(`五行相生:
${FiveElements.wood}生${FiveElements.fire}
${FiveElements.fire}生${FiveElements.earth}
${FiveElements.earth}生${FiveElements.metal}
${FiveElements.metal}生${FiveElements.water}
${FiveElements.water}生${FiveElements.wood}`)

console.log(`五行相剋:
${FiveElements.wood}生${FiveElements.earth}
${FiveElements.earth}生${FiveElements.water}
${FiveElements.water}生${FiveElements.fire}
${FiveElements.fire}生${FiveElements.metal}
${FiveElements.metal}生${FiveElements.wood}`)


其编译结果为:


//main.js
console.log(`五行相生:
${FiveElements.wood}生${FiveElements.fire}
${FiveElements.fire}生${FiveElements.earth}
${FiveElements.earth}生${FiveElements.metal}
${FiveElements.metal}生${FiveElements.water}
${FiveElements.water}生${FiveElements.wood}`);
console.log(`五行相剋:
${FiveElements.wood}生${FiveElements.earth}
${FiveElements.earth}生${FiveElements.water}
${FiveElements.water}生${FiveElements.fire}
${FiveElements.fire}生${FiveElements.metal}
${FiveElements.metal}生${FiveElements.wood}`);
//# sourceMappingURL=doc1.js.map
//enum.js
//# sourceMappingURL=doc2.js.map


外部枚举在编译阶段会被删除,常出现在声明文件中,可以结合常数枚举使用:


declare const enum FiveElements {
  metal = '金',
  wood = '木',
  water = '水',
  fire = '火',
  earth = '土'
}


结语

TypeScript基础篇到这里就结束了。对于学过强类型的语言(java、c、c++等)的同学来说TypeScript还是很简单好用的,很多概念都是从强类型语言中学习(是的,学习,程序员的事,怎么能叫抄呢😎😎)过来的;对于从一开始就学习前端的同学而言,一开始总是有点痛的,习惯了就好😋😋。