TypeScript知识整理


TypeScript 入门教程
TypeScript 中文手册

简介

  • js的超集:包含js的所有元素;能运行js代码;支持ES语法;是一种开源、跨平台的编程语言
  • 编译器编译为js代码,js解析器执行
  • 完全兼容js代码
  • 静态类型检查器:静态类型,更加严格的语法;减少运行时异常出现的几率;方便后期维护

什么是 TypeScript

①TypeScript 是静态类型

  • 动态类型是指在运行时才会进行类型检查,这种语言的类型错误往往会导致运行时错误。JavaScript 是一门解释型语言[4],没有编译阶段,所以它是动态类型。
  • 静态类型是指编译阶段就能确定每个变量的类型,这种语言的类型错误往往会导致语法错误。TypeScript 在运行前需要先编译为 JavaScript,而在编译阶段就会进行类型检查,所以 TypeScript 是静态类型

②TypeScript 是弱类型
类型系统按照「是否允许隐式类型转换」来分类,可以分为强类型和弱类型。
不管是在 JavaScript 中还是在 TypeScript 中都是可以正常运行的,运行时数字 1 会被隐式类型转换为字符串 ‘1’,加号 + 被识别为字符串拼接,所以打印出结果是字符串 ‘11’。

console.log(1 + '1');
// 打印出字符串 '11'

基础

基础数据类型

// 布尔值
let isDone: boolean = false;

// 数值
let myAge: number  = 24; // 十进制
let num1: number  = 0b00011000; // 二进制
let num2: number  = 0o30; // 八进制
let num3: number  = 0x18; // 十六进制
let notANumber: number = NaN;
let infinityNumber: number = Infinity;

// 字符串
let myName: string = 'hsy';

// 空值:JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 void 表示没有任何返回值的函数:
function alertName(): void {
    alert('My name is hsy');
}
// Null 和 Undefined:与 void 的区别是,undefined 和 null 是所有类型的子类型。
let u: undefined = undefined;
let n: null = null;
let a: number = undefined;
let b: string = null;

数组和对象

// 数组
// 方式一:「类型 + 方括号」表示法
let arr1: number[] = [1, 2, 3];
// 方式二:数组泛型
let arr2: Array<number> = [1, 2, 3];

// 对象
let obj: object = {};
obj = null;
obj = undefined;
obj = [];
obj = new String();
obj = String;

any和void

// any 任何类型
let a1: any = null;
a1 = 123;
a1 = 'any';
a1 = true;
a1 = {};
let arr: any[] = [1, 'a', true, [], {}];
// void 空值,表示没有任何返回值的函数
function foo(): void {
  console.log('hsy');
  // 相当于 return undefined;
}
console.log(foo()); // undefined
let v: void = undefined;

类型推论

如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型

  • 定义变量的时候,直接给变量赋值,则定义类型为对应赋值的类型, 即初始化没有指定类型但有赋值时,后续不能再赋值其他数据类型的值
  • 定义变量的时候,没有赋值,及赋值为understand或null时,该变量的声明类型为any
// 类型推论:ts在没有明确指定类型的时候会去推论一个类型
// 1. 定义变量的时候,直接给变量赋值,则定义类型为对应赋值的类型
// 即初始化没有指定类型但有赋值时,后续不能再赋值其他数据类型的值
let myFavoriteNumber = 'seven';
let myFavoriteNumber = 'eight';
// myFavoriteNumber = 7; // 报错
//  2. 定义变量的时候,没有赋值,及赋值为understand或null时,该变量的声明类型为any
let age = undefined;
age = 1;
age = '123';

联合类型|

联合类型(Union Types)表示取值可以为多种类型中的一种。

// 联合类型(Union Types):表示取值可以为多种类型中的一种。
// 联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型
// 当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法
let f1: string | number;
f1 = 'seven';
f1.length
f1 = 7;
// f1.length // 报错
console.log(f1.toString());
// f1 = true; // 报错

接口interface

接口:TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述

接口-对象类型

// 接口:TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
interface Person {
    name: string;
    age: number;
}
// 我们定义了一个接口 Person,接着定义了一个变量 tom,它的类型是 Person。这样,我们就约束了 tom 的形状必须和接口 Person 一致。
// 定义的变量比接口少了一些属性是不允许的
// 多一些属性也是不允许的
let tom: Person = {
    name: 'Tom',
    age: 25,
    // sex: 'man', // 报错
};
  • 定义的变量比接口少了一些属性是不允许的
  • 多一些属性也是不允许的
可选属性?

可选属性的含义是该属性可以不存在。
这时仍然不允许添加未定义的属性

interface Person {
    name: string;
    age: number;
    sex?: string,
}
let tom: Person = {
    name: 'Tom',
    age: 25,
    sex: 'man', // 有无这个属性都不会报错
    // height: 160, // 报错
};
任意属性[]

允许有任意个未指定的属性,

interface Person {
    name: string;
    age: number;
    sex?: string,
    [propName: string]: any;
}
let tom: Person = {
    name: 'Tom',
    age: 25,
    sex: 'man', // 有无这个属性都不会报错
    height: 160, // 不报错
    falg: true, // 不报错
};

需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:

interface Person {
    name: string;
    // age: number; // 报错
    sex?: string,
    [propName: string]: string,
}

一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型

interface Person {
    name: string;
    age: number;
    sex?: string,
    [propName: string]: number | string | boolean;
}
只读属性readonly

有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性

interface Person {
    readonly id: number;
    name: string;
    age: number;
    sex?: string,
    [propName: string]: any;
}
let tom: Person = {
    id: 1006,
    name: 'Tom',
    age: 25,
    sex: 'man', // 有无这个属性都不会报错
    height: 160, // 不报错
    falg: true, // 不报错
};
tom.age = 24;
console.log(tom.id);
// tom.id = 9527; // 报错

接口-数组类型

// 接口-数组类型
 interface INewArr {
  [index: number]: number
}
let fibonacci: INewArr = [1, 1, 2, 3, 5];

接口-函数类型

// 接口-函数类型
interface SearchFunc {
  // (参数:类型, ...):返回值的类型
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc = function(source: string, subString: string) : boolean {
  return source.search(subString) !== -1;
}

函数

定义函数的方法

在 JavaScript 中,有两种常见的定义函数的方式——函数声明(命名函数)函数表达式(匿名函数)

function sum(x, y) {
    return x + y;
}
// ts
function sumTs(x: number, y: number): number{
    return x + y;
}

// 函数表达式(匿名函数)
let mySum = function (x, y) {
    return x + y;
};
// ts
let mySumTs = function (x: number, y: number): number{
    return x + y;
}
  • 输入多余的(或者少于要求的)参数,是不被允许的
let mySumTs = function (x: number, y: number): number{
    return x + y;
}
// mySum(1,2,3); // 报错
// mySum(1); // 报错

完整函数表达式写法:
在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。

// let mySumTs = function (x: number, y: number): number{
//     return x + y;
// }
// 完整写法
let mySumTs : (x: number, y: number) => number = function (x: number, y: number): number{
    return x + y;
}

可选参数?

? 表示可选的参数。可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必需参数

function buildName(firstName: string, lastName?: string) {
    if (lastName) {
        return firstName + ' ' + lastName;
    } else {
        return firstName;
    }
}
let tomcat = buildName('Tom', 'Cat'); // 不报错
let tom = buildName('Tom'); // 不报错

默认参数=

function buildName2(firstName: string = 'Tom', lastName: string) {
    return firstName + ' ' + lastName;
}
let tomcat1 = buildName2('Tom', 'Cat');
let tomdog1 = buildName2(undefined,'Dog');
console.log(tomdog1); // Tom Dog

剩余参数…

function push(array: any[], ...items: any[]) {
  console.log(array); // [0]
  console.log(items); // [1, 2, 3]
    items.forEach(function(item) {
        array.push(item);
    });
}

let item = [0];
push(item, 1, 2, 3);
console.log(item); // [0, 1, 2, 3]

函数重载

重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。

// 函数重载:函数名相同,形参不同的多个函数
function reverse(x: number): number; // 函数重载声明
function reverse(x: string): string; // 函数重载声明
function reverse(x: number | string): number | string | void {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}
console.log(reverse(1006)); // 6001
console.log(reverse('hsy')); // ysh

类型断言as

类型断言(Type Assertion)可以用来手动指定一个值的类型。
语法 值 as 类型<类型>值

  • 联合类型可以被断言为其中一个类型
  • 父类可以被断言为子类
  • 任何类型都可以被断言为 any
  • any 可以被断言为任何类型
// 类型断言:可以手动指定一个类型
function getLength(x: string | number): number {
  if((x as string).length){
    return (<string>x).length;
  } else {
    return x.toString().length;
  }
}

用途:

  • ①将一个联合类型断言为其中一个类型:当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型中共有的属性或方法:
interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}

function getName(animal: Cat | Fish) {
    // animal.run() // 报错
    return animal.name;
}
interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}

function isFish(animal: Cat | Fish) {
    if (typeof (animal as Fish).swim === 'function') {
        return true;
    }
    return false;
}
  • ②将一个父类断言为更加具体的子类
interface ApiError extends Error {
    code: number;
}
interface HttpError extends Error {
    statusCode: number;
}

function isApiError(error: Error) {
    if (typeof (error as ApiError).code === 'number') {
        return true;
    }
    return false;
}
  • ③将任何一个类型断言为 any
// window.foo = 1; // 没有问题,但实际编译会报错
(window as any).foo = 1;
  • ④将 any 断言为一个具体的类型
function addFunc(x: any, y: any): any {
  return x + y;
}
let sum1 = addFunc(1, 2);
console.log(sum1); // 3
sum1 = 'hsy'; //  不报错
console.log(sum1); // hsy

let sum2 = addFunc(1, 2) as number;
console.log(sum2); // 3
sum2 = 1006;
// sum2 = 'hsy'; //  报错

进阶

类型别名type

用来给一个类型起个新名字。

// 类型别名
type str = string;
let s1: str = 'hsy';
let s2: string = 'hsy';
// 常用于给联合类型起别名
type all = string | number | boolean;
let all1: all = 123;
let all2: all = true;
let all3: all = 'hsy';
let all4: string | number | boolean = 'hsy';

字符串字面量类型type

用来约束取值只能是某几个字符串中的一个。

// 字符串字面量类型
type nameType = '张三' | '李四';
let name1: nameType = '张三';
let name2: nameType = '李四';
// let name3: nameType = '王五'; // 报错

元组

数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。

let numArr: number[] = [1, 2, 3, 4]; // 数组合并了相同类型的对象
numArr.push(5); // 不报错
// numArr.push('str'); // 报错
// 元组
let TArr: [number, string] = [1, 'hsy']; //元组 合并了不同类型的对象
TArr.push(2); // 不报错
TArr.push('str'); // 不报错
// TArr.push(true); // 报错

枚举enum

枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。

// 枚举
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
// 枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射
console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // 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 keys {
  // 没有赋值时,默认第一个为0后续依次递增
  a = 1,
  b, // 后面的值没有赋值时,会根据前面的值进行递增
  c = 6,
  d,
  e = 2,// 注意:未手动赋值的枚举项与手动赋值的重复了,TypeScript 是不会察觉到这一点的。最好不要出现这种覆盖的情况
}
console.log(keys.a); // 1
console.log(keys.b); // 2
console.log(keys.c); // 6
console.log(keys.d); // 7
console.log(keys.e); // 2

常数项和计算所得项

枚举项有两种类型:常数项计算所得项
前面的举例为常数项

计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错

// enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; // 常数项
enum color {
  red,
  blue = 'blue'.length, // 计算所得项
  // 注意:计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错
  pink = 10,
}

常数枚举const enum

常数枚举是使用 const enum 定义的枚举类型:

// 常数枚举是使用 const enum 定义的枚举类型:
const enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

// 常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员。
// 上例的编译结果是:
// var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];

外部枚举declare enum

外部枚举是使用 declare enum 定义的枚举类型

// 外部枚举是使用 declare enum 定义的枚举类型:
declare enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

// declare 定义的类型只会用于编译时的检查,编译结果中会被删除
// 上例的编译结果是:
// var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

类的概念

  • 类(Class):定义了一件事物的抽象特点,包含它的属性和方法
  • 对象(Object):类的实例,通过 new 生成
  • 面向对象(OOP)的三大特性:封装、继承、多态
  • 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据
  • 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
  • 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 Cat 和 Dog 都继承自 Animal,但是分别实现了自己的 eat 方法。此时针对某一个实例,我们无需了解它是 Cat 还是 Dog,就可以直接调用 eat 方法,程序会自动判断出来应该如何执行 eat
  • 存取器(getter & setter):用以改变属性的读取和赋值行为
  • 修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如 public 表示公有属性或方法
  • 抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
  • 接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口

属性和方法class、constructor

使用 class 定义类,使用 constructor 定义构造函数。

通过 new 生成新实例的时候,会自动调用构造函数。

// 类
class Animal {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    sayHi() {
        return `My name is ${this.name}`;
    }
}

let animal = new Animal('Jack');
console.log(animal.sayHi()); // My name is Jack

类的继承extends、super

使用 extends 关键字实现继承,子类中使用 super 关键字来调用父类的构造函数和方法。

// 类与类通过extends进行继承
// 子类可以通过super调用父类的方法
// 子类还可以重写父类的方法
class Cat extends Animal {
  constructor(name: string) {  
    super(name); // 调用父类的 constructor(name)
    console.log(this.name);
  }
  sayHi() {
    return 'Meow, ' + super.sayHi(); // 调用父类的 sayHi()
  }
}

let c = new Cat('Tom'); // Tom
console.log(c.sayHi()); // Meow, My name is Tom

存取器get、set

存取器可以控制对对象成员的访问

使用 getter 和 setter 可以改变属性的赋值和读取行为:

class Name {
  firstName: string;
  lastName: string;
  constructor(firstName: string, lastName: string,) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  // 读取器
  get name() {
    return this.firstName + '-' + this.lastName;
  }
  // 设置器
  set name(value) {
    let arr = value.split('-');
    this.firstName = arr[0];
    this.lastName = arr[1];
  }
}
let hsy = new Name('h','sy');
console.log(hsy.name); // h-sy
hsy.name = 'h-hh';
console.log(hsy.name); // h-hh

静态方法和静态属性static

使用 static 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用

class Animal {
  static des = 'this is Animal';
  static isAnimal(a) {
    return a instanceof Animal;
  }
}

let ani = new Animal();
console.log(Animal.des); // this is Animal
// console.log(ani.des); // 报错 undefined
console.log(Animal.isAnimal(ani)); // true
// ani.isAnimal(ani); // 报错 TypeError: a.isAnimal is not a function

访问修饰符public、private、protected

  • public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
  • private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
  • protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
class B {
  public name: string;
  private age: number;
  protected sex: string;
  public constructor(name: string, age: number, sex: string) {
    this.name = name;
    this.age = age;
    this.sex = sex;
  };
  public say(): void{
    console.log('my name is ' + this.name);
  };
  private say2(): void{
    console.log(this.age);
  };
  protected say3() : void {
    console.log(this.sex);
  }
}
class C extends B {
  constructor(name: string, age: number, sex: string) {
    super(name, age, sex);
  };
  // say2() {  // 报错,private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
  // // super.say2()
  // }
  say3(){
    super.say3()
  }
}
let ww = new B('王五', 18, '男');
let zl = new C('赵六', 19, '女');
// public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
console.log(ww.name); // 王五
console.log(zl.name); // 赵六
ww.say(); // my name is 王五
// private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
// ww.age; // 报错
// ww.say2(); // 报错
// protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
// ww.sex; // 报错
// ww.say3(); // 报错
// ww.say3(); // 报错, Property 'say3' is protected and only accessible within class 'B' and its subclasses.Vetur(2445)
zl.say3(); // 女

属性参数readonly

readonly 只读属性关键字,只允许出现在属性声明或索引签名或构造函数中。

class Animal {
  // readonly name; // 构造函数参数有声明时不需要
  // readonly protected private public在构造函数参数有声明时就是创建并初始化,不需要在外部再次声明
  public constructor(readonly name: string) {
    this.name = name;
  }
}

let an = new Animal('Jack');
console.log(an.name); // Jack
// an.name = 'Tom'; // 报错

抽象类abstract

抽象类是不允许被实例化
abstract 用于定义抽象类和其中的抽象方法。

// 抽象类
abstract class Animal {
  abstract des: string; // 抽象属性
  abstract name: string; // 抽象属性
  // public constructor(public name: string) {
  //   this.name = name;
  // }
  public abstract sayHi(); // 抽象方法,不能有具体实现,为子类服务
}
// 抽象类是不允许被实例化的
// let a = new Animal('Jack'); // 报错

class Cat extends Animal {
  public des: string; // 必须定义父抽象类的抽象属性
  public constructor(public name: string) {
    super();
    this.name = name;
  }
  public eat() {
    console.log(`${this.name} is eating.`);
  };
  public sayHi() { // 必须具体实现父抽象类的抽象方法
    console.log(`Meow, My name is ${this.name}`);
  } 
}

let cat: Cat = new Cat('Tom'); // 类可以作为类型给变量进行定义
cat.eat(); // Tom is eating.
cat.sayHi(); // Meow, My name is Tom

类与接口

类实现接口implements

实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements 关键字来实现。这个特性大大提高了面向对象的灵活性。

// 类实现接口
// 接口
interface Alarm {
    alert(): void;
}
interface Light {
    lightOn(): void;
    lightOff(): void;
}
// 类
class Door {
}
// 实现接口
class Car implements Alarm {
    alert() {
        console.log('Car alert');
    }
}
// 继承并实现接口
class SecurityDoor extends Door implements Alarm {
    alert() {
        console.log('SecurityDoor alert');
    }
}
// 实现多个接口
class Car2 implements Alarm, Light {
    alert() {
        console.log('Car alert');
    }
    lightOn() {
        console.log('Car light on');
    }
    lightOff() {
        console.log('Car light off');
    }
}

接口继承接口

接口与接口之间可以是继承关系

// 接口
interface Alarm {
    alert(): void;
}
// 接口继承接口
interface LightableAlarm extends Alarm {
    lightOn(): void;
    lightOff(): void;
}
class Car3 implements LightableAlarm {
    alert() {
        console.log('Car alert');
    }
    lightOn() {
        console.log('Car light on');
    }
    lightOff() {
        console.log('Car light off');
    }
}

接口继承类

// 接口继承类
// 继承类的实例属性和实例方法
class Point {
  constructor(public x: number, public y: number) {
    this.x = x;
    this.y = y;
  };
  public sum(): void {
    console.log(this.x + this.y);
  }
}
interface Point3d extends Point {
    z: number;
}
let point3d: Point3d = {
  x: 1,
  y: 2,
  z: 3,
  sum: (): void => {
    console.log('');
  }
}
console.log(point3d); //{x: 1, y: 2, z: 3, sum: ƒ}

声明合并

函数的合并(函数重载)

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(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 Alarm {
    price: number;
    alert(s: string): string;
}
interface Alarm {
    // price: string;  // 类型不一致,会报错
    price: number;  // 虽然重复了,但是类型都是 `number`,所以不会报错
    weight: number;
    alert(s: string, n: number): string;
}
// 相当于:
// interface Alarm {
//     price: number;
//     weight: number;
//     alert(s: string): string;
//     alert(s: string, n: number): string;
// }

类的合并

类的合并与接口的合并规则一致。

泛型

泛型指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。(定义的时候不指定类型,使用的时候指定

基本使用

// 泛型
function createArray<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    console.log(result);
    
    return result;
}
createArray<string>(3, 'x'); // ['x', 'x', 'x']
createArray(3, 3); // [3, 3, 3] 没有指定类型时走类型推断

多个类型参数

// 多个类型参数
function swap<T, U>(tuple: [T, U]): [U, T] {
  console.log([tuple[1], tuple[0]]);
  return [tuple[1], tuple[0]]
}
swap([7, 'seven']); // ['seven', 7]
swap<boolean, number>([true, 1]); // [1, true]

泛型约束

在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法:

function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);
    return arg;
}

// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'T'.

上例中,泛型 T 不一定包含属性 length,所以编译的时候报错了

我们可以对泛型进行约束,只允许这个函数传入那些包含 length 属性的变量。这就是泛型约束:

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}

// loggingIdentity(7); // 报错,number类型没有length属性
loggingIdentity('7');

泛型接口

使用接口的方式来定义一个函数需要符合的形状:

interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc = function(source: string, subString: string) {
    return source.search(subString) !== -1;
} 

也可以使用含有泛型的接口来定义函数的形状:

// 泛型接口
interface CreateArrayFunc {
  <T>(length: number, value: T): Array<T>;
}
let createArrayfunc: CreateArrayFunc = function<T>(n:number , val: T): Array<T> {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
        result[i] = val;
    }
    console.log(result);
    return result;
}
createArray(6,'hsy'); // ['hsy', 'hsy', 'hsy', 'hsy', 'hsy', 'hsy']
createArray(2,true); // [true, true]
createArray(3,123); // [123, 123, 123]

泛型类

与泛型接口类似,泛型也可以用于类的类型定义中:

// 泛型类
class P<T> {
  constructor(public name: string, public des: T){
    this.name = name;
    this.des = des;
    console.log(this);
  }
}
const p1 = new P('hsy', true); // P {name: 'hsy', des: true}
const p2 = new P('hsy', 'hsy'); // P {name: 'hsy', des: 'hsy'}
const p3 = new P('hsy', 1006); // P {name: 'hsy', des: 1006}
  • 25
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值