一、简介
1.1 什么是 TypeScript
TypeScript是JavaScript的一个超集,主要提供了类型系统和对ES6的支持TypeScript是由微软开发的一款开源的编程语言TypeScript是Javascript的超级,遵循最新的ES6、Es5规范。TypeScript扩展了JavaScript的语法TypeScript更像后端java、C#这样的面向对象语言可以让js开发大型企业项目
1.2 为什么选择 TypeScript
Typescript和es6、es5关系

TypeScript 增加了代码的可读性和可维护性
- 类型系统实际上是最好的文档,大部分的函数看看类型的定义就可以知道如何使用了
- 可以在编译阶段就发现大部分错误,这总比在运行时候出错好
- 增强了编辑器和
IDE的功能,包括代码补全、接口提示、跳转到定义、重构等
TypeScript 非常包容
TypeScript是JavaScript的超集,.js文件可以直接重命名为.ts即可- 即使不显式的定义类型,也能够自动做出类型推论
- 可以定义从简单到复杂的几乎一切类型
- 即使
TypeScript编译报错,也可以生成JavaScript文件 - 兼容第三方库,即使第三方库不是用
TypeScript写的,也可以编写单独的类型文件供TypeScript读取
TypeScript 拥有活跃的社区
- 大部分第三方库都有提供给
TypeScript的类型定义文件 Google开发的Angular2就是使用TypeScript编写的TypeScript拥抱了ES6规范,也支持部分ESNext草案的规范- 最新的
Vue、React也可以集成TypeScript
TypeScript 的缺点
- 有一定的学习成本,需要理解接口(
Interfaces)、泛型(Generics)、类(Classes)、枚举类型(Enums)等前端工程师可能不是很熟悉的概念 - 短期可能会增加一些开发成本,毕竟要多写一些类型的定义,不过对于一个需要长期维护的项目,
TypeScript能够减少其维护成本 - 集成到构建流程需要一些工作量
- 可能和一些库结合的不是很完美
1.3 安装 TypeScript
typescript 安装
npm i typescript -g |
全局安装完成后,我们新建一个
hello.ts的ts文件
// hello.ts内容 |
接下来我们在命令行输入
tsc hello.ts来编译这个ts文件,然后会在同级目录生成一个编译好了的hello.js文件
// hello.js内容 |
那么我们每次都要输
tsc hello.ts命令来编译,这样很麻烦,能否让它自动编译?答案是可以的,使用vscode来开发,需要配置一下vscode就可以。
首先我们在命令行执行
tsc --init来生成配置文件,然后我们在目录下看到生成了一个tsconfig.json文件

这个
json文件里有很多选项
target是选择编译到什么语法module则是模块类型outDir则是输出目录,可以指定这个参数到指定目录
更多细节 https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/tsconfig.json.html
接下来我们需要开启监控了,在
vscode任务栏中

1.4 Hello TypeScript
将以下代码复制到
hello.ts中
function sayHello(person: string) { |
tsc hello.ts |
//这时候会生成一个编译好的文件 hello.js: |
TypeScript中,使用:指定变量的类型,:的前后有没有空格都可以
TypeScript只会进行静态检查,如果发现有错误,编译的时候就会报错TypeScript编译的时候即使报错了,还是会生成编译结果,我们仍然可以使用这个编译之后的文件
二、基础
2.1 原始数据类型
JavaScript的类型分为两种:原始数据类型(Primitive data types)和对象类型(Object types)。
- 原始数据类型包括:
布尔值、数值、字符串、null、undefined以及ES6中的新类型Symbol。
本节主要介绍前五种原始数据类型在
TypeScript中的应用
2.1.1 布尔值
布尔值是最基础的数据类型,在
TypeScript中,使用boolean定义布尔值类型
let isDone: boolean = false; |
注意,使用构造函数
Boolean创造的对象不是布尔值
let createdByNewBoolean: boolean = new Boolean(1); |
- 事实上
new Boolean()返回的是一个Boolean对象:
let createdByNewBoolean: Boolean = new Boolean(1); |
- 直接调用
Boolean也可以返回一个boolean类型:
let createdByBoolean: boolean = Boolean(1); |
- 在
TypeScript中,boolean是JavaScript中的基本类型,而Boolean是JavaScript中的构造函数。其他基本类型(除了null和undefined)一样
2.1.2 数值
使用
number定义数值类型
let decLiteral: number = 6; |
//编译结果: |
其中
0b1010 和0o744是ES6中的二进制和八进制表示法,它们会被编译为十进制数字
2.1.3 字符串
使用
string定义字符串类型:
let myName: string = 'Tom'; |
2.1.4 空值
JavaScript没有空值(Void)的概念,在TypeScript中,可以用void表示没有任何返回值的函数
function alertName(): void { |
声明一个
void类型的变量没有什么用,因为你只能将它赋值为undefined和null:
let unusable: void = undefined; |
2.1.5 Null 和 Undefined
在
TypeScript中,可以使用null和undefined来定义这两个原始数据类型:
let u: undefined = undefined; |
undefined类型的变量只能被赋值为undefined,null类型的变量只能被赋值为null
- 与
void的区别是,undefined和null是所有类型的子类型。也就是说undefined类型的变量,可以赋值给number类型的变量
// 这样不会报错 |
而
void类型的变量不能赋值给number类型的变量:
let u: void; |
2.2 任意值Any
如果是一个普通类型,在赋值过程中改变类型是不被允许的
let myFavoriteNumber: string = 'seven'; |
但如果是
any类型,则允许被赋值为任意类型。
let myFavoriteNumber: any = 'seven'; |
任意值的属性和方法
在任意值上访问任何属性都是允许的:
let anyThing: any = 'hello'; |
也允许调用任何方法:
let anyThing: any = 'Tom'; |
可以认为,声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值
未声明类型的变量
变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型:
let something; |
等价于
let something: any; |
2.3 类型推论
如果没有明确的指定类型,那么
TypeScript会依照类型推论(Type Inference)的规则推断出一个类型
什么是类型推论
以下代码虽然没有指定类型,但是会在编译的时候报错:
let myFavoriteNumber = 'seven'; |
事实上,它等价于:
let myFavoriteNumber: string = 'seven'; |
TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查
let myFavoriteNumber; |
2.4 联合类型
联合类型(
Union Types)表示取值可以为多种类型中的一种
// 简单例子 |
let myFavoriteNumber: string | number; |
- 联合类型使用
|分隔每个类型。 - 这里的
let myFavoriteNumber: string | number的含义是,允许myFavoriteNumber的类型是string或者number,但是不能是其他类型
访问联合类型的属性或方法
当
TypeScript不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法
function getLength(something: string | number): number { |
访问
string和number的共有属性是没问题的
function getString(something: string | number): string { |
联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型
let myFavoriteNumber: string | number; |
- 上例中,第二行的
myFavoriteNumber被推断成了string,访问它的length属性不会报错。 - 而第四行的
myFavoriteNumber被推断成了number,访问它的length属性时就报错了
2.5 对象的类型——接口
2.5.1 简单例子
在
TypeScript中,我们使用接口(Interfaces)来定义对象的类型
什么是接口
- 在面向对象语言中,接口(
Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implements)。 TypeScript中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
接口一般首字母大写
interface Person { |
上面的例子中,我们定义了一个接口
Person,接着定义了一个变量tom,它的类型是Person。这样,我们就约束了tom的形状必须和接口Person一致
定义的变量比接口少了一些属性是不允许的
interface Person { |
多一些属性也是不允许的
interface Person { |
可见,赋值的时候,变量的形状必须和接口的形状保持一致。
2.5.2 可选属性
有时我们希望不要完全匹配一个形状,那么可以用可选属性
可选属性的含义是该属性可以不存在
interface Person { |
interface Person { |
2.5.3 任意属性
有时候我们希望一个接口允许有任意的属性,可以使用如下方式
interface Person { |
- 使用
[propName: string]定义了任意属性取string类型的值 - 需要注意的是,一旦定义了任意属性,那么确定属性和可选属性都必须是它的子属性
interface Person { |
- 上例中,任意属性的值允许是
string,但是可选属性age的值却是number,number不是string的子属性,所以报错了。 - 另外,在报错信息中可以看出,此时
{ name: 'Tom', age: 25, gender: 'male' }的类型被推断成了{ [x: string]: string | number; name: string; age: number; gender: string; },这是联合类型和接口的结合
2.5.4 只读属性
有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用
readonly定义只读属性
interface Person { |
上例中,使用
readonly定义的属性id初始化后,又被赋值了,所以报错了
注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候
interface Person { |
- 上例中,报错信息有两处,第一处是在对
tom进行赋值的时候,没有给id赋值。 - 第二处是在给
tom.id赋值的时候,由于它是只读属性,所以报错了
2.6 数组的类型
在
TypeScript中,数组类型有多种定义方式,比较灵活。
2.6.1「类型 + 方括号」表示法
最简单的方法是使用「类型 + 方括号」来表示数组:
let fibonacci: number[] = [1, 1, 2, 3, 5]; |
数组的项中不允许出现其他的类型
let fibonacci: number[] = [1, '1', 2, 3, 5]; |
- 上例中,
[1, '1', 2, 3, 5]的类型被推断为(number | string)[],这是联合类型和数组的结合。 - 数组的一些方法的参数也会根据数组在定义时约定的类型进行限制
let fibonacci: number[] = [1, 1, 2, 3, 5]; |
上例中,
push方法只允许传入number类型的参数,但是却传了一个string类型的参数,所以报错了
2.6.2 数组泛型
也可以使用数组泛型(
Array Generic)Array<elemType>来表示数组
let fibonacci: Array<number> = [1, 1, 2, 3, 5]; |
2.6.3 用接口表示数组
interface NumberArray { |
NumberArray表示:只要index的类型是number,那么值的类型必须是number
2.6.4 any 在数组中的应用
一个比较常见的做法是,用
any表示数组中允许出现任意类型:
let list: any[] = ['poetries', 22, { website: 'http://blog.poetries.top' }]; |
2.6.5 类数组
类数组(
Array-like Object)不是数组类型,比如arguments
function sum() { |
事实上常见的类数组都有自己的接口定义,如
IArguments,NodeList,HTMLCollection等:
function sum() { |
2.7 函数的类型
2.7.1 函数声明
在
JavaScript中,有两种常见的定义函数的方式——函数声明(Function Declaration)和函数表达式(Function Expression)
// 函数声明(Function Declaration) |
一个函数有输入和输出,要在
TypeScript中对其进行约束,需要把输入和输出都考虑到,其中函数声明的类型定义较简单
function sum(x: number, y: number): number { |
注意,输入多余的(或者少于要求的)参数,是不被允许的:
function sum(x: number, y: number): number { |
function sum(x: number, y: number): number { |
2.7.2 函数表达式
如果要我们现在写一个对函数表达式(
Function Expression)的定义,可能会写成这样
let mySum = function (x: number, y: number): number { |
这是可以通过编译的,不过事实上,上面的代码只对等号右侧的匿名函数进行了类型定义,而等号左边的
mySum,是通过赋值操作进行类型推论而推断出来的。如果需要我们手动给mySum添加类型,则应该是这样
// =>左边 (x: number, y: number) 是输入类型 |
注意不要混淆了 TypeScript 中的 => 和 ES6 中的 =>
在
TypeScript的类型定义中,=>用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
2.7.3 用接口定义函数的形状
我们也可以使用接口的方式来定义一个函数需要符合的形状
interface SearchFunc { |
需要注意的是,可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必须参数了
function buildName(firstName?: string, lastName: string) { |
2.7.4 参数默认值
在
ES6中,我们允许给函数的参数添加默认值,TypeScript会将添加了默认值的参数识别为可选参数
function buildName(firstName: string, lastName: string = 'Cat') { |
此时就不受「可选参数必须接在必需参数后面」的限制了
function buildName(firstName: string = 'Tom', lastName: string) { |
2.7.5 剩余参数
ES6 中,可以使用
...rest的方式获取函数中的剩余参数(rest参数)
function push(array, ...items) { |
事实上,items 是一个数组。所以我们可以用数组的类型来定义它
function push(array: any[], ...items: any[]) { |
注意,rest 参数只能是最后一个参数
2.7.6 函数重载
- 重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。
比如,我们需要实现一个函数
reverse,输入数字123的时候,输出反转的数字321,输入字符串'hello'的时候,输出反转的字符串'olleh'
利用联合类型,我们可以这么实现
function reverse(x: number | string): number | string { |
然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串
这时,我们可以使用重载定义多个 reverse 的函数类型
function reverse(x: number): number; |
- 上例中,我们重复定义了多次函数
reverse,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。
注意,
TypeScript会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面
2.8 类型断言
类型断言(
Type Assertion)可以用来手动指定一个值的类型。
语法
<类型>值 |
在
tsx语法(React的jsx语法的ts版)中必须用后一种
例子:将一个联合类型的变量指定为一个更加具体的类型
当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法
function getLength(something: string | number): number { |
而有时候,我们确实需要在还不确定类型的时候就访问其中一个类型的属性或方法,比如
function getLength(something: string | number): number { |
上例中,获取
something.length的时候会报错
此时可以使用类型断言,将 something 断言成 string
function getLength(something: string | number): number { |
类型断言的用法如上,在需要断言的变量前加上
<Type>即可
类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的
function toBoolean(something: string | number): boolean { |
2.9 声明文件
当使用第三方库时,我们需要引用它的声明文件
2.9.1 声明(declare)语句
假如我们想使用第三方库,比如
jQuery,我们通常这样获取一个id是foo的元素
$('#foo'); |
但是在
TypeScript中,我们并不知道$或jQuery是什么东西
jQuery('#foo'); |
这时,我们需要使用
declare关键字来定义它的类型,帮助TypeScript判断我们传入的参数类型对不对
declare var jQuery: (selector: string) => any; |
declare定义的类型只会用于编译时的检查,编译结果中会被删除
//上例的编译结果是: |
2.9.2 声明文件(约定.d.ts后缀)
通常我们会把类型声明放到一个单独的文件中,这就是声明文件
// jQuery.d.ts |
- 我们约定声明文件以
.d.ts为后缀。 - 然后在使用到的文件的开头,用
「三斜线指令」///表示引用了声明文件
/// <reference path="./jQuery.d.ts" /> |
2.9.3 第三方声明文件
当然,
jQuery的声明文件不需要我们定义了,已经有人帮我们定义好了:jQuery in DefinitelyTyped
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/jquery/index.d.ts |
- 我们可以直接下载下来使用,但是更推荐的是使用工具统一管理第三方库的声明文件- 社区已经有多种方式引入声明文件,不过
TypeScript 2.0推荐使用@types来管理。 @types的使用方式很简单,直接用npm安装对应的声明模块即可,以jQuery举例
npm install @types/jquery --save-dev |
可以在这个页面搜索你需要的声明文件
2.10 内置对象
JavaScript中有很多内置对象,它们可以直接在TypeScript中当做定义好了的类型
内置对象是指根据标准在全局作用域(
Global)上存在的对象。这里的标准是指ECMAScript和其他环境(比如DOM)的标准
2.10.1 ECMAScript 的内置对象
ECMAScript 标准提供的内置对象有
Boolean、Error、Date、RegExp等
我们可以在 TypeScript 中将变量定义为这些类型:
let b: Boolean = new Boolean(1); |
更多的内置对象,可以查看 MDN 的文档
而他们的定义文件,则在 TypeScript 核心库的定义文件中
2.10.2 DOM 和 BOM 的内置对象
DOM 和 BOM 提供的内置对象有
Document、HTMLElement、Event、NodeList等。
TypeScript 中会经常用到这些类型
let body: HTMLElement = document.body; |
它们的定义文件同样在 TypeScript 核心库的定义文件中
2.10.3 TypeScript 核心库的定义文件
TypeScript 核心库的定义文件中定义了所有浏览器环境需要用到的类型,并且是预置在 TypeScript 中的
当你在使用一些常用的方法的时候,
TypeScript实际上已经帮你做了很多类型判断的工作了,比如
Math.pow(10, '2'); |
上面的例子中,
Math.pow必须接受两个number类型的参数。事实上Math.pow的类型定义如下
interface Math { |
再举一个
DOM中的例子
document.addEventListener('click', function(e) { |
上面的例子中,
addEventListener方法是在TypeScript核心库中定义的
interface Document extends Node, GlobalEventHandlers, NodeSelector, DocumentEvent { |
所以
e被推断成了MouseEvent,而MouseEvent是没有targetCurrent属性的,所以报错了
注意,TypeScript 核心库的定义中不包含 Node.js 部分
2.10.4 用 TypeScript 写 Node.js
Node.js不是内置对象的一部分,如果想用TypeScript写Node.js,则需要引入第三方声明文件
npm install @types/node --save-dev |
三、进阶
3.1 类型别名
类型别名用来给一个类型起个新名字
type Name = string; |
上例中,我们使用 type 创建类型别名。
类型别名常用于联合类型
3.2 字符串字面量类型
字符串字面量类型用来约束取值只能是某几个字符串中的一个
type EventNames = 'click' | 'scroll' | 'mousemove'; |
- 上例中,我们使用
type定了一个字符串字面量类型EventNames,它只能取三种字符串中的一种。
注意,类型别名与字符串字面量类型都是使用 type 进行定
3.3 元组
- 数组合并了相同类型的对象,而元组(
Tuple)合并了不同类型的对象。 - 元组起源于函数编程语言,在这些语言中频繁使用元组。
3.3.1 简单的例子
定义一对值分别为
string和number的元组
let user: [string, number] = ['poetries', 22]; |
当赋值或访问一个已知索引的元素时,会得到正确的类型
let user: [string, number]; |
也可以只赋值其中一项
let user: [string, number]; |
3.3.2 越界的元素
当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型
let user: [string, number]; |
3.4 枚举
枚举(
Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等
3.4.1 简单的例子
枚举使用
enum关键字来定义:
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; |
枚举成员会被赋值为从
0开始递增的数字,同时也会对枚举值到枚举名进行反向映射
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; |
事实上,上面的例子会被编译为
var Days; |
3.4.2 手动赋值
我们也可以给枚举项手动赋值
enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat}; |
上面的例子中,未手动赋值的枚举项会接着上一个枚举项递增
如果未手动赋值的枚举项与手动赋值的重复了,TypeScript 是不会察觉到这一点的
enum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat}; |
上面的例子中,递增到
3的时候与前面的Sun的取值重复了,但是TypeScript并没有报错,导致Days[3]的值先是"Sun",而后又被"Wed"覆盖了。编译的结果是
var Days; |
所以使用的时候需要注意,最好不要出现这种覆盖的情况。
手动赋值的枚举项可以不是数字,此时需要使用类型断言来让
tsc无视类型检查 (编译出的js仍然是可用的):
enum Days {Sun = 7, Mon, Tue, Wed, Thu, Fri, Sat = <any>"S"}; |
var Days; |
当然,手动赋值的枚举项也可以为小数或负数,此时后续未手动赋值的项的递增步长仍为
1:
enum Days {Sun = 7, Mon = 1.5, Tue, Wed, Thu, Fri, Sat}; |
3.4.3 常数项和计算所得项
枚举项有两种类型:常数项(
constant member)和计算所得项(computed member)
前面我们所举的例子都是常数项,一个典型的计算所得项的例子:
enum Color {Red, Green, Blue = "blue".length}; |
上面的例子中,
"blue".length就是一个计算所得项。
上面的例子不会报错,但是如果紧接在计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错
enum Color {Red = "red".length, Green, Blue}; |
3.4.4 常数枚举
常数枚举是使用
const enum定义的枚举类型
const enum Directions { |
常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员
//上例的编译结果是: |
// 假如包含了计算成员,则会在编译阶段报错: |
3.4.5 外部枚举
外部枚举(
Ambient Enums)是使用declare enum定义的枚举类型
declare enum Directions { |
- 之前提到过,
declare定义的类型只会用于编译时的检查,编译结果中会被删除。
上例的编译结果是:
var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]; |
- 外部枚举与声明语句一样,常出现在声明文件中。
- 同时使用
declare和const也是可以的:
declare const enum Directions { |
// 编译结果: |
3.5 类
3.5.1 类的概念
类相关的概念做一个简单的介绍
- 类(
Class):定义了一件事物的抽象特点,包含它的属性和方法 - 对象(
Object):类的实例,通过new生成 - 面向对象(
OOP)的三大特性:封装、继承、多态 - 封装(
Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据 - 继承(
Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性 - 多态(
Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如Cat和Dog都继承自Animal,但是分别实现了自己的eat方法。此时针对某一个实例,我们无需了解它是Cat还是Dog,就可以直接调用eat方法,程序会自动判断出来应该如何执行eat - 存取器(
getter & setter):用以改变属性的读取和赋值行为 - 修饰符(
Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如public表示公有属性或方法 - 抽象类(
Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现 - 接口(
Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口
3.5.2 public private 和 protected
TypeScript可以使用三种访问修饰符(Access Modifiers),分别是public、private和protected
public修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是public的private修饰的属性或方法是私有的,不能在声明它的类的外部访问protected修饰的属性或方法是受保护的,它和private类似,区别是它在子类中也是允许被访问的
class Animal { |
上面的例子中,
name被设置为了public,所以直接访问实例的name属性是允许的。
很多时候,我们希望有的属性是无法直接存取的,这时候就可以用 private 了
lass Animal { |
上面的例子编译后的代码是:
var Animal = (function () { |
使用
private修饰的属性或方法,在子类中也是不允许访问的:
class Animal { |
而如果是用
protected修饰,则允许在子类中访问
class Animal { |
3.5.3 抽象类
abstract用于定义抽象类和其中的抽象方法。
什么是抽象类?
首先,抽象类是不允许被实例化的
abstract class Animal { |
上面的例子中,我们定义了一个抽象类
Animal,并且定义了一个抽象方法sayHi。在实例化抽象类的时候报错了。
其次,抽象类中的抽象方法必须被子类实现
abstract class Animal { |
上面的例子中,我们定义了一个类
Cat继承了抽象类Animal,但是没有实现抽象方法sayHi,所以编译报错了。
下面是一个正确使用抽象类的例子:
abstract class Animal { |
上面的例子中,我们实现了抽象方法 sayHi,编译通过了。
需要注意的是,即使是抽象方法,
TypeScript的编译结果中,仍然会存在这个类,上面的代码的编译结果是:
var __extends = (this && this.__extends) || function (d, b) { |
3.5.4 类的类型
给类加上
TypeScript的类型很简单,与接口类似:
class Animal { |
3.6 类与接口
3.6.1 类实现接口
实现(
implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用implements关键字来实现。这个特性大大提高了面向对象的灵活性
举例来说,门是一个类,防盗门是门的子类。如果防盗门有一个报警器的功能,我们可以简单的给防盗门添加一个报警方法。这时候如果有另一个类,车,也有报警器的功能,就可以考虑把报警器提取出来,作为一个接口,防盗门和车都去实现它
interface Alarm { |
一个类可以实现多个接口
interface Alarm { |
上例中,
Car实现了Alarm和Light接口,既能报警,也能开关车灯
3.6.2 接口继承接口
接口与接口之间可以是继承关系
interface Alarm { |
上例中,我们使用
extends使LightableAlarm继承Alarm
3.6.3 接口继承类
接口也可以继承类:
class Point { |
3.6.4 混合类型
可以使用接口的方式来定义一个函数需要符合的形状
interface SearchFunc { |
有时候,一个函数还可以有自己的属性和方法
interface Counter { |
3.7 泛型
泛型(
Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
3.7.1 简单的例子
首先,我们来实现一个函数
createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值
function createArray(length: number, value: any): Array<any> { |
- 上例中,我们使用了之前提到过的数组泛型来定义返回值的类型。
- 这段代码编译不会报错,但是一个显而易见的缺陷是,它并没有准确的定义返回值的类型:
Array<any>允许数组的每一项都为任意类型。但是我们预期的是,数组中每一项都应该是输入的value的类型。
这时候,泛型就派上用场了:
function createArray<T>(length: number, value: T): Array<T> { |
上例中,我们在函数名后添加了
<T>,其中T用来指代任意输入的类型,在后面的输入value: T和输出Array<T>中即可使用了
接着在调用的时候,可以指定它具体的类型为 string。当然,也可以不手动指定,而让类型推论自动推算出来
function createArray<T>(length: number, value: T): Array<T> { |
3.7.2 多个类型参数
定义泛型的时候,可以一次定义多个类型参数:
function swap<T, U>(tuple: [T, U]): [U, T] { |
上例中,我们定义了一个
swap函数,用来交换输入的元组
3.7.3 泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法
function loggingIdentity<T>(arg: T): T { |
上例中,泛型
T不一定包含属性length,所以编译的时候报错了。
这时,我们可以对泛型进行约束,只允许这个函数传入那些包含
length属性的变量。这就是泛型约束
interface Lengthwise { |
上例中,我们使用了
extends约束了泛型T必须符合接口Lengthwise的形状,也就是必须包含length属性。
此时如果调用
loggingIdentity的时候,传入的arg不包含length,那么在编译阶段就会报错了
interface Lengthwise { |
多个类型参数之间也可以互相约束:
function copyFields<T extends U, U>(target: T, source: U): T { |
上例中,我们使用了两个类型参数,其中要求
T继承U,这样就保证了U上不会出现T中不存在的字段
3.7.4 泛型接口
可以使用接口的方式来定义一个函数需要符合的形状
interface SearchFunc { |
当然也可以使用含有泛型的接口来定义函数的形状
interface CreateArrayFunc { |
进一步,我们可以把泛型参数提前到接口名上
interface CreateArrayFunc<T> { |
注意,此时在使用泛型接口的时候,需要定义泛型的类型
3.7.5 泛型类
与泛型接口类似,泛型也可以用于类的类型定义中
class GenericNumber<T> { |
3.7.6 泛型参数的默认类型
在
TypeScript 2.3以后,我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用
function createArray<T = string>(length: number, value: T): Array<T> { |
3.8 声明合并
如果定义了两个相同名字的函数、接口或类,那么它们会合并成一个类型
3.8.1 函数的合并
我们可以使用重载定义多个函数类型
function reverse(x: number): number; |
3.8.2 接口的合并
接口中的属性在合并时会简单的合并到一个接口中
interface Alarm { |
相当于:
interface Alarm { |
注意,合并的属性的类型必须是唯一的
interface Alarm { |
interface Alarm { |
接口中方法的合并,与函数的合并一样
interface Alarm { |
相当于:
interface Alarm { |
3.8.3 类的合并
类的合并与接口的合并规则一致
四、工程
4.1 tsconfig.json
编译选项
你可以通过
compilerOptions来定制你的编译选项
{ |
4.2 TypeScript 编译
运行
tsc -p ./path-to-project-directory。tsc -w来启用TypeScript编译器的观测模式,在检测到文件改动之后,它将重新编译
指定需要编译的文件
{ |
使用 include 和 exclude 选项来指定需要包含的文件,和排除的文件
{ |
五、一些例子演示
5.1 定义ajax请求数据接口
interface Config{ |
5.2 函数类型接口-对方法约束
// 函数类型接口:对方法传入的参数 以及返回值进行约束 批量约束 |
5.3 可索引接口:数组、对象的约束(不常用)
5.3.1 可索引接口-对数组的约束
interface UserArr{ |
5.3.2 可索引接口-对对象的约束
interface UserObj{ |
5.3.3 类类型接口:对类的约束
- 抽象类抽象有点相似
interface Animal{ |
5.4 接口的扩展
接口继承接口 类实现接口
interface Animal{ |
5.5 泛型类接口
5.5.1 泛型类 泛型方法
- 泛型:软件工程中,我们不仅要创建一致的定义良好的
API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。 - 在像
C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。 - 通俗理解:泛型就是解决类接口方法的复用性、以及对不特定数据类型的支持(类型校验)
// 只能返回string类型的数据 |
//同时返回 string类型 和number类型 any可以解决这个问题 |
//any放弃了类型检查,传入什么 返回什么。比如:传入number 类型必须返回number类型 传入 string类型必须返回string类型 |
T表示泛型,具体什么类型是调用这个方法的时候决定的
// T表示泛型,具体什么类型是调用这个方法的时候决定的 |
function getData<T>(value:T):any{ |
泛型类
泛型类:比如有个最小堆算法,需要同时支持返回数字和字符串
a - z两种类型。 通过类的泛型来实现
// 基本写法 但是不能传入字符串 |
类的泛型
// 通过泛型改写 可以同时传入number 字符串等 |
5.5.2 泛型接口
1. 方式1
interface ConfigFn{ |
2. 方式2
interface ConfigFn<T>{ |