我为什么会这样念念又不忘 / 你用什么牌的箭刺穿我心脏
我也久经沙场 / 戎马生涯 / 依然 / 被一箭刺伤
——李荣浩《念念又不忘》
接下来我会分上、下两篇文章介绍 TypeScript。
我也是 TypeScript 初学者,这两篇文章是我的学习笔记,来源于一个系列的免费视频,视频不错,如果你觉得看视频学习更快,可以点击这里看到。
TypeScript 与 JavaScript 有何区别呢?简单点说,就是前者引入了类型约束,能实现代码的静态检查,而且能提供更加完善的代码提示功能;除此之外,还引入了接口、抽象类、枚举、访问控制修饰符、泛型等语法,让我们写出的代码更具健壮性和可扩展性。
上、下两篇的文章内容安排如下:
- 《上篇》
- 基本类型
- 对象类型
- 扩展类型
- 《下篇》
- 面向对象编程
- 泛型
《上篇》定义为基础篇,《下篇》定义为深入篇。
我们先从基础篇开始学习。
开发环境
TypeScript 脚本以 .ts
后缀结尾。我使用 VSCode 学习 TypeScript,写好 TypeScript 代码后, 需要将其编译为 JavaScript 代码才能运行。在此之前,我们先要安装 Node.js 环境。
然后安装 TypeScript 编译环境。
$ npm install -g typescript
$ tsc --version
复制代码
编译文件的指令如下:
// 执行完下列语句后,会在同级目录下看到一个 `HelloWorld.js` 文件,
// 就是编译完成后的文件啦
$ tsc HelloWorld.ts
复制代码
codepen.io 中也支持 TypeScript 的书写。新建一个新的 Pen 后,将 JavaScript 一栏的预处理器设置成 TypeScript 即可,你也可以实时查看到编译之后的代码,不过代码提示效果不是很强。
基本类型
我们先从最简单的基本数据类型(Primitive)说起。
ECMAScript 提供了六种基本数据类型:布尔、数值、字符串、Null、Undefined 和 Symbol。
而 TypeScript 针对上述的每一种类型,都提供了对应的类型字面值:boolean、number、string、null、undefiend 和 symbol(ES6 中引入,本系列两篇不对 symbol 做介绍)。
在 TypeScript 中,使用 : type
语法为变量指定类型:
// 声明一个布尔值类型变量 `isMale`,初始值为 `true`
let isMale: boolean = true;
// 声明一个字符串类型变量 `myName`,初始值为 `'Alex'`
let myName: string = 'Alex';
// 声明一个数值类型变量 `myAge`,初始值为 `20`
let myAge: number = 20;
// 声明一个 Null 类型变量 `myGirlFriend`,值为 `null`
let myGirlFriend: null = null;
// 声明一个 Undefined 类型变量 `myHouse`,值为 `undefined`
let myHouse: undefined = undefined;
复制代码
注意:TypeScript 提供的类型字面值都是小写形式,注意与首字母大写的形式区分,后者是 JavaScript 原生提供的构造器函数。
有时,一个变量的类型并不局限于一种。比如,一个变量的值可以是字符串,也可以是数值。这时就要用到“联合类型”了。
联合类型使用竖线 |
分隔,表示某个变量可以给予其中任意一种类型值。
下例中,声明了一个变量 foo
,它的值可以是一个字符串,也可以是一个数值。
// 此处声明了一个变量 `foo`,可以是字符串,也可以是数值
let foo: string | number = 'bar'; // 初始值给了字符串 `'bar'`
foo = 123; // 接下来将 `foo` 重新赋值为 `123`
复制代码
对象类型
除了基本类型,工作中最常处理就是对象了。那么如何在 TypeScript 中指定对象类型呢?
定义对象
在 TypeScript 中,使用接口,也就是关键字 interface
来描述对象的形状,也就是对象的类型。“接口”在传统的面向对象编程的语言里,比如 Java,表示“行为的抽象”,而在 TypeScript 对此稍有不同,接口不仅可以表示行为的抽象,还可以用来定义对象类型。
接下来,我们定义一个类型 Person
(按照约定,首字母大写):
// 用接口声明一个类型 `Person`
interface Person {name: string;age: number;
}// 将变量 `alex` 声明为 `Person` 类型
let alex: Person = {name: 'Alex',age: 20
};
复制代码
注意:定义接口时,属性之间可以用分号
;
、也可以用逗号,
分隔,甚至什么都不加也可以。
我们定义了一个类型 Person
,并将变量 alex
的类型声明为 Person
。那么,在给 alex
赋值时,必须严格符合类型定义:赋值对象必须由一个字符串属性 name
和一个数值属性 age
组成,缺少或多出的属性,都会提示错误。
// 会提示出错(缺少一个属性)
let alex: Person = {name: 'Alex';
};// 会提示错误(多了一个属性)
let alex: Person = {name: 'Alex',age: 20,gender: 'male'
};
复制代码
定义类型时,如果想要表示某个属性是可选的,则使用 ?: type
语法声明。
// 类型 `Person` 的 `name` 属性是可选的
interface Person {name: string;age?: number;
}// 因为 `age` 是可选属性,所以赋值时不给也行
let alex: Person = {name: 'Alex'
};
复制代码
除了定义可选属性,还可以定义“任意属性”。
所谓的任意属性,就是我们不确定将来会添加的属性名称是什么,但是会提前定义允许添加的属性,在不确定未来这个属性名的情况下,限制这个属性的类型。
// Person 中定义了一个任意属性,属性类型是 `any`
interface Person {name: string;age?: number;[propName: string]: any;
}// 我们给变量 `alex` 添加了一个任意属性 `gender`
let alex: Person = {name: 'Alex',gender: 'male
};
复制代码
我们使用 [propName: string]: any
的形式,定义了一个 any
类型的任意属性。
注意,任意属性的类型,必须是上面的已知属性
name
和age
类型的超集,否则会提示出错。 比如,上面我们可以将上面任意属性的类型any
修改为string | number
也是可以的。
说完对象,再来介绍数组。
数组类型
在数组上指定类型,本质上是限制数组成员的类型。在 TypeScript 中,使用 type[]
语法指定数组成员的类型。
下面定义了一个数组,限制其成员只能是字符串。
// 此处定义了一个数组 `myFriends`,其成员限定为只能是字符串
let myFriends: string[] = ['Alex', 'Bob'];
复制代码
如果数组成员允许包含多个类型值,则使用 (type1 | type2 | ...)[]
的语法声明。
// 此处定义了一个数组 `foo`,其成员可以是字符串,也可以是数值
let foo: (string | number)[] = ['Alex', 'Bob', 123];
复制代码
如果数组的成员是对象,则有如下两种声明方式:
// 方式 1:通过预定义好的类型,声明 `friends` 成员类型
interface Person {name: string;
}
let friends: Person[] = [ { name: 'Alex' }, { name: 'Bob' } ];// 方式 2:直接通过字面量类型的形式,声明 `friends` 成员类型
let friends: {name: string
}[] = [ { name: 'Alex' }, { name: 'Bob' } ];
复制代码
接下来,进入到扩展类型的学习。
扩展类型
Typescript 除了支持 JavaScript 类型之外,还提供了一些扩展类型。
首先,我们来介绍下字面量类型。
字面量类型
当我们像下面这样赋值时:
let seven: number = 7;
// ❌ 这样赋值的话会有错误,提示`Type '"Seven"' is not assignable to type 'number'`
seven = 'Seven';
复制代码
注意,这里的 'Seven'
被当成了一个类型,说 'Seven'
类型不能赋值给数值类型变量 seven
。
其实这里的 'Seven'
是一个字符串字面量类型。
Typescript 中的字面量类型包括:字符串字面量、数值字面量和布尔值字面量。
除此之外,我们还可以使用 type
关键字定义一个新的类型:
// 此处我们定义了一个新类型 `FavoriteNumber`,这个新类型仅由三个值的集合组成
type FavoriteNumber = 'One' | 'Two' | 'Seven';
// 接下来,将变量 `seven` 声明为类型 `FavoriteNumber`,并赋值为 `'Seven'`
let seven: FavoriteNumber = 'Seven';
复制代码
以上定义了一个类型 FavoriteNumber
,它由三个值的集合组成(一个类型通常至少包含两个或以上的值)。变量 seven
被声明为该类型,赋值为 'Seven'
,这是一个有效值。如果我们给 seven
赋了一个不在 FavoriteNumber
类型之内的值,就会报错,比如:
// ❌ 此处会报错:`Type '123' is not assignable to type 'FavoriteNumber'.`
let seven: FavoriteNumber = 123;
复制代码
枚举
在介绍枚举类型之前,我们先来看下面的代码:
// 此处定义了两个变量 `errorColor` 和 `infoColor`
let dangerColor = 'red';
let infoColor = 'blue';// 添加一个判断传入颜色是否是危险色的函数
function isItDangerColor(color) {return color === 'red';
}// 接下来,调用函数 `isItRed`
isItDangerColor(dangerColor); // true
isItDangerColor(infoColor); // false
复制代码
上面这一小段的代码逻辑很简单,但有个小小的问题——如果表示危险的颜色由 'red'
变为 'pink'
了,那么我们就需要修改两个地方的代码。
针对这个问题,我们稍微修改下代码,引入一个表示颜色集合的变量 Colors
来解决:
// 我们使用 `Colors` 这个变量来存储逻辑中使用到的颜色集合
const Colors = {Danger: 'red',Info: 'blue'
};// 在余下的业务逻辑中,我们使用颜色变量代替之前的颜色字面值
let dangerColor = Colors.Danger;
let infoColor = Colors.Info;function isItDangerColor(color) {return color === Colors.Danger;
}
复制代码
这样带来的便利是,如果 Colors.Danger
所代表的颜色值变了,只要在 Colors
中修改一下就可以了。
进一步思考,可以知道,这里的 Colors.Danger
和 Colors.Info
的值具体是什么并不重要,只要能保证它们彼此不相等就行。比如,我们写成下面这样:
// 这样定义 `Colors` 依旧不会影响逻辑
const Colors = {Danger: 0,Info: 1
};
复制代码
这种定义变量的方式,用 TypeScript 中的枚举来改写就是下面这样的:
// 枚举变量使用 `enum` 关键字定义
// 此处定义了一个枚举变量 `Colors`
enum Colors {Danger,Info
}
复制代码
上面一段代码经过编译后,得到的 JavaScript 源码如下:
var Colors;
(function (Colors) {Colors[Colors["Danger"] = 0] = "Danger";Colors[Colors["Info"] = 1] = "Info";
})(Colors || (Colors = {}));
复制代码
由此可知,
enum Colors {Danger,Info
}// 等价于var Colors = {0: 'Danger',1: 'Info','Danger': 0,'Info': 1
};
复制代码
我们修改下初始的例子,使用枚举来组织逻辑:
enum Colors {Danger, // 对应的值是 0Info, // 对应的值是 1Success // 对应的值是 2
}// 我们将函数 `isItDanger` 的参数 `color` 类型约束为 `Colors`
// 说明此函数只接收 `Colors` 中列举的值
function isItDanger(color: Colors): boolean {return color === Colors.Danger;
}// 接下来使用 `Colors.Info` 调用 `isItDanger` 函数
isItDanger(Colors.Info); // false
复制代码
除了使用默认的索引值,我们还可以为枚举变量中的每一项指定值:
enum Colors {Red, // 对应的值是 0Blue = 3, // 将 Blue 值指定为 3Green // 接上面的 3,此处的值是 4
}enum Colors {Red = 'red', // 对应的值是 'red'Blue = 'blue', // 对应的值是 'blue'Green = 'green' // 对应的值是 'green'
}
复制代码
上篇完。