1、什么是TypeScript?
一个拥有类型系统的JavaScript超集,可以编译成纯JavaScript。
三个要点:
- 类型检查,严格的类型检查,在编译阶段发现问题,不必把问题带到线上
- 语言扩展,包含ES6及未来提案中的特性,也从其他语言借鉴特性,比如接口和抽象类
- 工具属性,可以编译成纯JavaScript,可以在任何浏览器上运行,更像一个工具
2、为什么要使用TypeScript?
- 更好的IDE支持,比如VS Code 的自动补全,导航和重构功能,使得接口定义可以代替文档,
- 可以提升效率,降低维护成本
- 可以构建类型思维,从代码的编写者蜕变成代码的设计者
3、静态类型语言和动态类型语言
- 静态类型语言:在编译阶段确定所有变量的类型
- 动态类型语言:在执行阶段确定所有变量的语言
4、TypeScript的数据类型
除了ES6包含的数据类型,TypeScript又新增了
- void
- any
- never
- 元组: 特殊的数组类型,限制了数组的元素的类型和数量
- 枚举
- 高级类型
1)类型注解:相当于强类型语言中的类型声明。
2)联合类型,通过一个竖线来分开
3)元组越界问题,设置好的元组,可以通过push方法给元组新增元素,元素也可以被添加到元组中,但是在访问的时候报错
4)一个变量被声明为undefined/null类型,就不能再赋值其他类型,只能被赋值为它本身。
但是undefined/null类型是所有类型的子类型,是可以赋值给其他类型的,可以通过tsconfig.json里的 strictNullChecks 设置成 false 来实现
5)在JavaScript中,void是一个操符,可以让任何表达式返回undefined,比如最简单的返回undefined的方式是void 0,而且undefined不是一个保留字,可以通过设置一个undefined变量来覆盖全局的undefined变量。在TypeScript中,void类型就是指,没有任何返回值的类型。
6)没有声明的变量就是any类型
7)never是指永远不会有返回值的类型
8)枚举类型,就是一组有名字的常量集合,
9)枚举类型分为:
- 数字枚举和字符串枚举,数字枚举的实现原理是反向映射,成员名称分别作为key和value
- 字符串枚举,只有成员名称当做了key,
- 异构枚举,包含数字枚举和字符串枚举,不推荐使用
10)枚举成员的值是一个只读类型,不能修改
枚举成员的类型分为2类 - 常量枚举,会在编译的时候计算出结果,以常量的形式出现在运行环境
- 没有初始值的情况
- 对已有枚举成员的引用
- 常量表达式
- 需要被计算的枚举成员:一些非常量的表达式,不会再编译时计算,会在运行时计算。(在computed member成员后面的枚举成员,一定要有一个初始值,否则会报错)
11)常量枚举,用const声明的枚举类型为常量枚举,会在编译之后被删除
作用: 当我们不需要一个对象,而只需要对象的值的时候,就可以声明常量枚举,减少我们在编译环境的代码
12)枚举类型:两种不同类型的枚举,是不可以进行比较的。
5、对象类型接口
接口可以用来约束对象,函数,类的一种结构,是代码协作的契约
类型断言 x as string 明确的告诉编译器,这个x就是一个string类型,
字符串索引签名
interface result {
name: string;
age: number;
[x: string]: any; // 字符串索引签名
}
接口成员的属性:
- 可选属性:?
- 只读属性:readonly name:string
不能知道接口的属性的数目,就可以使用,可索引类型的接口
interface StringArray {
[index: number]: string;
}
interface Names {
[x: string]: string;
}
// 混用
interface Person {
[x: string]: string;
[y: number]: string; // 数字类型的属性值的类型一定要是string类型的子类型,js会进行类型转换
}
接口定义函数
let add = (x: number, y: number) => number;
interface Add {
(x: number, y: number): number;
}
// 类型别名
type Add = (x: number, y: number) => number;
类型别名,就是为这个类型起一个名字
混合类型接口
interface Lib {
(): void;
version: string;
dosomething(): void;
}
let lib: Lib = (() => {}) as Lib;
lib.version = '1.0.0';
lib.dosomething = () => {}
// 👆是一个lib单例,如果想生成多个lib,可以写一个函数
let createLib = () => {
let lib: Lib = (() => {}) as Lib;
lib.version = '1.0.0';
lib.dosomething = () => {};
return lib;
}
6、函数类型接口
1)在TS中,函数的形参合实参必须一一对应,可选参数必须在必选参数之后。且参数可以设置默认值,在必选参数之前,默认参数不可省略,必须传入undefined才能获取到其默认值。剩余参数…rest, 剩余参数是以数组方式来处理的
2)函数重载:如果一个函数,函数名一样,参数个数不一样,就实现了一个函数重载。
TS的函数重载,要求先定义一系列的函数声明,然后在一个类型最宽泛的声明中实现这个函数
function add(...rest: number[]): number;
function add(...rest: string[]): string;
function add(...rest: any[]): any {
let first = rest[0]
if (typeof first === 'string') {
return rest.join('');
}
if (typeof first === 'number') {
return rest.reduce((pre, cur) => pre + cur, 0)
}
};
TS 在处理函数重载的时候,回去查询一个列表,就是上面定义的函数声明的列表,会从第一个开始匹配。
7、TS中的类
1)无论在TS还是在ES中,类成员的属性都是实例属性,不是原型属性。类成员的方法都是原型方法
2)在TS中,实例的属性必须有初始值或者在构造函数中有默认值
3)类的继承,使用extends关键字,必须在子类里的构造函数中调用super(),并在super后再使用this
4)类的成员修饰符
- public,默认属性都是public
- private,私有属性,只能在类的本身被调用,不能被类的子类和实例调用,给构造函数添加private属性,就会使这个类既不能被实例化也不能被继承
- protected,只能在类和子类中使用,不能在实例中使用,构造函数添加protected属性,就会使这个类不能被实例化,只能用于继承,这就声明了一个抽象类。
- readonly,只读属性不可更改,必须初始化
- static,静态属性,只能由类名来调用,不能通过实例来调用
除了类的成员可以添加修饰符以外,构造函数的参数也能添加修饰符。这样会使参数自动变为实例的属性
5)抽象类:只能用于继承,不能用于实例化的类。使用abstruct来修饰类名。可以在抽象类里添加一个抽象方法,不具体实现该方法。
抽象类呢,用于抽离代码的共性,有利于代码的复用。
6)多态:使用抽象类的抽象方法,可以实现不同的类里面不同的方法的绑定。
7)this类型,类的成员类型,可以返回一个this,这样就可以很容易实现链式调用。在子类中的成员方法也返回this时,在实例化的时候就可以形成一个多态,该this既可以调用子类里的方法,也可以调用父类里的方法。
8、类和接口的关系
1)接口可以约束类的成员类型
interface Human {
name: string;
eat(): void;
}
class Asian implements Human {
constructor(name: string) {
this.name = name;
}
name: string
eat() {}
}
- 类实现接口的时候,必须实现接口中所有的属性。
- 接口只能约束类的公有成员
- 接口不能约束类的构造函数
2)接口的继承,使用extends继承,继承接口时,多个接口时,用逗号分开,继承类时,使用该接口的类需要有类的属性
接口在抽离类的成员的时候,会把public,private和protected成员都抽离出来