类型别名
用于给一个类型起一个新名字,常用于联合类型
type FirstName = string;
type LastName = () => string;
type Name = FirstName | LastName;
字符串字面量类型
用来约束变量的取值,使其只能是某几个字符串中的一个
type Name = 'jack' | 'lilei' | 'anna';
function sayHi(name: Name){
//do something
}
sayHi('jack') //true
sayHi('小明') //error
元组
数组中放置的是相同类型的变量或者对象,而元组中放的都是不同类型的对象,例如
let x: [string, number] = ['aa', 11] //设定了一个第一项为字符串,第二项为数字的数组
x[0] = 'aa'
x[1] = 11
- 元组可以接受赋值
let x: [string, number]
x[0] = 'hello' //true
- 但是要注意的是直接对元组进行初始化时,需要提供元组内所有类型指定的项
let tom: [string, number];
tom = ['Tom']; //false
tom = ['Tom', 1] //true
- 给元组添加原来组内不存在的元素时,元素的类型会被限制为元组内原有类型的联合类型
let person: [string, number]
person = ['jack', 18]
person.push('你好') //true
person.push(true) //error
枚举
语法
枚举使用 enum 关键字来进行定义
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}
默认赋值
枚举成员会被默认的赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射
Days["Sun"] === 0 //true
Days["Mon"] === 1 //true
Days["Tue"] === 2 //true
Days["Sat"] === 6 //true
Days[0] === "Sun" //true
Days[1] === "Mon" //true
Days[2] === "Tue" //true
Days[6] === "Sat" //true
此时,上方代码实际上最后会被编译为
var Days;(function (Days) {
Days[Days["Sun"] = 0] = "Sun";
Days[Days["Mon"] = 1] = "Mon";
Days[Days["Tue"] = 2] = "Tue";
Days[Days["Wed"] = 3] = "Wed";
Days[Days["Thu"] = 4] = "Thu";
Days[Days["Fri"] = 5] = "Fri";
Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));
手动赋值
可以对枚举项进行手动赋值
enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};
Days["Sun"] === 7 // true
Days["Mon"] === 1 // true
Days["Tue"] === 2 // true
Days["Sat"] === 6 // true
未赋值的枚举项会被自动赋值为上一项的值 +1,然后往后递增
- 注意,typescript 不会发现手动赋值和默认赋值之间的冲突,因此要注意避免
enum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat};
Days["Sun"] === 3 // true
Days["Wed"] === 3 // true
Days[3] === "Sun" // false
Days[3] === "Wed" // false
- 如果手动复制的值是小数或者负数,后续的项依旧是递增+1
enum Days {Sun = 3, Mon = 1.5, Tue, Wed, Thu, Fri, Sat};
Days["Tue"] === 2.5 //true
常数项和计算所得项
enum Color {Red, Green, Blue = "blue".length};
此时,计算所得项 Color["Blue"] = 4
- 但是要注意的是,计算所得项的后面不能跟未手动赋值的项,否则会报错。也就是说,计算所得项必须是枚举类型中的最后一个值
enum Color {Green, Blue = "blue".length, Red}; //error
常数枚举
就是使用 const enum 定义的枚举类型
const enum Directions {Up, Down, Left, Right}
假如包含了计算所得项,那么就会报错
const enum Directions {Up, Down, Left, Right = 'right'.length} //error
外部枚举
就是使用 declare enum 定义的枚举类型
declare enum Directions {Up, Down, Left, Right}
可以同时使用 declare 和 const 进行声明
declare const enum Directions {Up, Down, Left, Right}
假如包含计算所得项,也会报错
declare enum Directions {Up, Down, Left, Right = 'right'.length} //error
类
- 类(Class):定义了一件事物的抽象特点,包括他的一些属性和方法
- 对象(Object):类的实例化,通过 new 关键字来生成
- 面向对象的三大特性:继承、封装、多态
ES6 中的 class
使用 constructor 来定义构造函数
class Person{
public name
constructor(name){
this.name = name
}
sayHi(){
return `Hello, my name is ${this.name}`
}
}
let demo = new Person('jack')
console.log(demo.sayHi()) //Hello, my name is jack
类的继承
使用 extends 关键字来实现继承,同时,子类中使用 super 关键字来调用父类中的构造函数
class Boy extends Person{
constructor(name){
super(name) //调用父类的 constructor(name) 方法
console.log(this.name)
}
sayHi(){
return super.sayHi() + ', i am a boy'
}
}
let LiMing = new Boy('LiMing') //LiMing
console.log(LiMing.sayHi()) //Hello, my name is LiMing, i am a boy
存取器
使用 getter 和 setter 来改变属性的赋值和读取
class Animal {
constructor(name) {
this.name = name;
}
get name() {
return 'Jack';
}
set name(value) {
console.log('setter: ' + value);
}}
let a = new Animal('Kitty'); // setter: Kitty
a.name = 'Tom'; // setter: Tom
console.log(a.name); // Jack
static 静态方法
无需实例化的对象,只能通过对应类来调用
class Animal {
static isAnimal(a) {
return a instanceof Animal;
}
}
let a = new Animal('Jack');
Animal.isAnimal(a); // true
a.isAnimal(a); // TypeError: a.isAnimal is not a function
TypeScript 中类的用法
public
代表公有属性,可以在任何地方访问到,所有属性和方法默认都是 public 的
class Animal {
protected name;
public constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
console.log(this.name);
}
}
let a = new Animal('cat'); // true
private
代表私有属性,不能在声明类以外的地方访问,在子类中也不能访问到
class Animal {
public name;
private constructor(name) {
this.name = name;
}
}
class Cat extends Animal { //error, Cannot extend a class 'Animal'. Class constructor is marked as private.
constructor(name) {
super(name);
}
}
let a = new Animal('Jack'); //error, Constructor of class 'Animal' is private and only accessible within the class declaration.
protected
受保护的属性,类似于 private,但是 protected 可以在它的子类中访问到
class Animal {
public name;
protected constructor(name) {
this.name = name;
}
}
class Cat extends Animal { //true
constructor(name) {
super(name);
}
}
let a = new Animal('Jack'); //error, Constructor of class 'Animal' is protected and only accessible within the class declaration
readyonly 属性
只读属性,只允许出现在类型、属性声明中。但是要注意的是,readonly 需要放置在其他修饰符的后方
class Animal {
public readonly name;
public constructor(name) {
// something
}
}
抽象类
abstract 用于定义抽象类和其中的抽象方法
什么是抽象类?
首先,抽象类是不允许实例化的,也就是不允许使用 new 关键词来进行实例化
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
let a = new Animal('Jack'); //error, Cannot create an instance of an abstract class.
其次,抽象类中的抽象方法必须被子类实现,如果没实现,就会出现报错
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
class Cat extends Animal {
public eat() {
console.log(`${this.name} is eating.`);
}
}
let cat = new Cat('Tom'); //error, Cat' does not implement inherited abstract member 'sayHi' from class 'Animal'.
需要声明 sayHi()
class Cat extends Animal {
public eat() {
console.log(`${this.name} is eating.`);
}
public sayHi() {
console.log(`Meow, My name is ${this.name}`);
}
}
类的类型定义
与接口类似
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
sayHi(): string {
return `My name is ${this.name}`;
}
}
let a: Animal = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack
类与接口
类继承接口
实现(implements)是面向对象中的一个重要的概念。因为一个类只能继承另一个类,而这时如果不同类之间有一些共有的特性,这时候就可以把这些特性提取出来,用 implements 关键字来实现。
interface Alarm {
alert(): void;
}
class Door {}
class SecurityDoor extends Door implements Alarm {
alert() {
console.log('SecurityDoor alert');
}
}
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
同时,一个类可以实现(implements)多个接口
interface Alarm {
alert(): void;
}
interface Light {
lightOn(): void;
lightOff(): void;
}
class Car implements Alarm, Light {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
可以理解为,类 class 是一种对象,继承类相当于被继承类的进化体,而接口 interface 中就是不同类中共有的“行为”部分,比如古代人和现代人都会吃饭,而实现这种“行为”的方法就是通过 implements
接口继承接口
接口与接口之间也可以继承 extends
interface Person {
eat(): void;
}
interface Man extends Person{
sayHi(): void;
walk(): void;
}
接口继承类
class Demo {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
interface NewDemo extends Demo {
z: number;
}
let demo: NewDemo = {x: 1, y: 2, z: 3};
- 为什么别的面向对象语言不支持接口继承类,TypeScript 支持呢?
- 实际上,我们再声明 class Demo 时,除了会创建一个 Demo 类之外,同时也创建了一个 Demo 的类型(Type),所以,Demo 既可以当一个类来使用(new Demo),也可以当作一个类型来使用(let demo: Demo)
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
let x: Point = {x:1, y:2}
- 所以,接口继承类 和 接口继承接口,并没有什么本质上的区别
- 注意的是,class 类定义时,会有一个 constructor 构造函数,而如果是接口 interface 定义的,则是没有 constructor 的部分的
泛型
泛型指的是,在定义函数、接口、类的时候,不预先去指定具体的类型,而是在使用的时候再去指定类型的行为。
function createArray(length: number, value: any): Array<any> {
let result = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
- 这样定义会有一个问题,没有准确的定义返回值的类型,
Array<any>返回的是一个由 any 类型组成的数组,但我们期望的是数组中的每一项都应该与参数中的 value 类型相同,这时候就需要用到泛型
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray<string>(3, 'x'); // ['x', 'x', 'x']
- 其中,我们用 T 来代替原来的 any,这样就可以确保 value 和 Array 中的每一项的类型都是相同的了
- 在调用函数
createArray(3, 'x')时,输入的 'x' 会让其自动推算出其类型是 string
多个类型参数
可以同时定义多个类型参数
function Demo<A, B>(array: [A, B]): [B, A] {
return [array[1], array[0]];
}
Demo([7, 'seven']); // ['seven', 7]
泛型约束
在函数内使用泛型变量时,由于不能确定其正确的类型,所以不能随意操作它的属性和方法
function demo<T>(arg: T): T {
console.log(arg.length);
return arg;
}
//error, 泛型变量 arg 并不一定包含属性 length
这时,可以通过泛型约束的方法,只允许该函数传入包含有 length 的变量,这就是泛型约束
interface Base{
length: number;
}
function demo<T extends Base>(arg: T): T {
console.log(arg.length);
return arg;
}
demo('demo')//true
demo(1) //error, Argument of type 'number' is not assignable to parameter of type 'Base'.
此时泛型 T,继承了 Base 接口,也就约束了 T 必须去包含一个 length 的属性,如果传入的参数不包含 length 属性,就会报错。
同时,多个泛型之间也可以互相约束
function copy<A extends B, B>(target: A, source: B): T {
//do Something
}
此时,A 是继承于 B 的,这样可以确保不会出现 B 有的字段,而 A 没有的情况
泛型接口
interface Demo {
<T>(length: number, value: T): Array<T>;
}
//或者
interface Demo<T> {
(length: number, value: T): Array<T>;
}
let demo: Demo;
demo = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
demo(3, 'x'); // ['x', 'x', 'x']
泛型类
class Demo<T> {
value: T;
add: (x: T, y: T) => T;
}
let myDemo = new Demo<number>();
泛型参数的默认类型
function Demo<T = string>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
声明合并
如果定义了两个名字相同的函数、接口、类,那么他们会自动合并成一个类型
函数的合并(重载)
function demo(x: number): number;
function demo(x: string): string;
function demo(x: number | string): number | string {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
接口的合并
接口中的属性在合并时会简单的合并到一个接口中
interface Hello{
hi: string
}
interface Hello{
sayHi: () => void
}
那也就相当于
interface Hello{
hi: string,
sayHi: () => void
}
- 但是要注意的是,要进行合并的属性,必须是相同的类型
interface Hello{
hi: string
}
interface Hello{
hi: string //true
sayHi: () => void
}
interface Hello{
hi: string
}
interface Hello{
hi: number //error,类型不一致
sayHi: () => void
}
接口中的函数方法的合并与普通函数的合并是相同的
interface Hello{
hi: string,
sayHi(name: string): void
}
interface Hello{
hi: string,
sayHi(name: string, age: number): void
}
也就相当于
interface Hello{
hi: string,
sayHi(name: string): void
sayHi(name: string, age: number): void
}