Skip to content
本页目录

你需要知道的TypeScript(类、函数、泛型)

类的基本定义与使用

js
 class Water {
    // 声明属性
    name: string
    // 构造方法
    constructor(name:string) {
        this.name = name
    }
    // 方法
    hello () {
        return "Hello, " + this.name
    }
 }

 // 创建类的实例
 let water = new Water('water')
 // 调用实例的方法
 water.hello()

类的继承

js
class Animal {
    hello(name:string) {
        console.log(`Animal hello ${name}`)
    }
}

class Dog extends Animal {
    say() {
        console.log('dog')
    }
}

const dog = new Dog()
dog.say()
dog.hello('dog')

使用extends关键字实现继承,子类中使用super关键字来调用父类的构造函数和方法。从基类中继承了属性和方法。这里的Dog是一个派生类,它派生自Animal基类,通过extends关键字。派生类通常被称作子类,基类通常被称作超类。

因为Dog继承了Animal的能力,因此可以创建一个Dog的实例,该实例拥有say()hello()实例方法

js
class Preson {
    name:string
    constructor(name:string){
        this.name = name
    }
    say(hello:string='hello'){
       console.log(`${this.name} and ${hello}`)
    }
}
class Water extends Person {
    constructor(name:string){
        // 调用父类型构造方法
        super(name)
    }
    // 重写父类型的方法
    say(hello:string = 'water'){
        super.say(hello)
    }
}
class WaterToo extends Person {
    constructor(name:string){
        // 调用父类型构造方法
        super(name)
    }

    // 重写父类型的方法
    say(hello:string = 'waterToo'){
        // 调用父类型方法
        super.say(hello)
    }

    // 子类自己的方法
    ower(){
        console.log('ower')
    }
}

const water = new Water('water')
water.say()
const waterToo = new WaterToo('waterToo')
waterToo.say()

// 父类型引用指向子类型的实例 => 多态
const newWaterToo:Person = new WaterToo('newOne')
newWaterToo.say()

// 如果子类型没有扩展自己的方法,可以让子类型引用指向父类型的实例
const otherWater:Water = new Person('otherWater')
otherWater.say()

// 如果子类有扩展自己的方法,就不能让子类引用指向父类型的实例
const errWater:WaterToo = new Person('errWater')
// err
errWater.say() 

使用extends关键字创建了Person的两个子类:WaterWaterToo,子类中拥有自己的构造函数,并且在自己的构造函数中必须调用super来执行基类的构造函数。而且在构造函数里访问this的属性之前,一定要先调用super

存取器getter/setter

TypeScript支持通过getter/setter来改变属性的赋值和读取,能让你有效的控制对对象成员的访问

js
class Person {
    firstName: string = "a"
    lastName: string = "b"
    get fullName() {
        return this.firstName + "-" + this.lastName
    }
    set fullName(value){
        const names = value.split("-")
        this.firstName = names[0]
        this.lastName = names[1]
    }
}

const p = new Person()
console.log(p.fullName)

p.firstName = "c"
p.lastName = "d"

console.log(p.fullName)

p.fullName = "eeee"
console.log(p.firstName,p.lastName)

类的静态成员

静态属性,是类对象的属性,非静态属性,是类的实例对象的属性。静态成员分为静态属性和静态方法,在变量或方法的前面加上static关键字来定义静态属性或静态方法。

  • 静态属性
js
class Animal {
    static num = 42
    constructor() {

    }
}

  • 静态方法
js
class Animal {
    static isAnimal(a) {
        return a instanceof Animal
    }
}

访问修饰符

ts中有三种修饰符,分别是publicprivateprotected

  • public修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是public
  • private修饰的属性或方法是私有的,不能在声明它的类的外部访问
  • protected修饰的属性或方法是受保护的,它和private类似,区别是它在子类中也是允许被访问的

public

typeScript中,成员默认都是public

js
class Water {
    public name
    public constructor(name) {
        this.name = name
    }
}

let water = new Water('water')
console.log(water.name)
water.name='a'

private

私有的,不能在声明它的类的外部访问

js
class Water {
    private name
    public constructor(name) {
        this.name = name
    }
}
let water = new Water("water")
console.log(water.name)
// 会报错
a.name = 'water1'

但是编译以后的代码就没有这个限制了,因为编译之后就变成了js了。

使用private修饰的属性或方法,在子类中也是不允许访问的:

js
class Person {
    private name
    public constructor(name){
        this.name = name
    }
}

// 会报错
class Water extends Person {
    constructor(name) {
        super(name)
        console.log(this.name)
    }
}

protected

protected修饰符与private修饰符的行为很相似,但有一点不同,protected成员在派生类中仍然可以访问

js
class Person {
  public name: string;

  public constructor(name: string) {
    this.name = name;
  }

  public jump(height: number = 0) {
    console.log(`${this.name} jump ${height}m`);
  }
}

class Water extends Person {
  private age: number = 18;
  protected sex: string = "";

  jump(height: number = 5) {
    console.log("Water jumping...");
    super.jump(height);
  }
}

class Student extends Person {
  jump(height: number = 6) {
    console.log("Student jumping...");

    console.log(this.sex); // 子类能看到父类中受保护的成员
    // console.log(this.age) //  子类看不到父类中私有的成员

    super.jump(distance);
  }
}

console.log(new Person("abc").name); // 公开的可见
// console.log(new Person('abc').sex) // 受保护的不可见
// console.log(new Person('abc').age) //  私有的不可见


readonly修饰符

readonly关键字将属性设置为只读的,只读属性必需在声明时候或构造函数里被初始化

js
class Person {
  readonly name: string = "abc";
  constructor(name: string) {
    this.name = name;
  }
}

let water = new Person("water");
// water.name = 'peter' // error


参数属性

在上面的例子中,Person类里定义一个只读成员name和一个参数为name的构造函数,并且立刻将name的值赋给this.name,这种情况经常会遇到,参数属性可以方便的让在一个地方定义并初始化一个成员。

js
class Person {
    constructor(readonly name:string){}
}
const water = new Person('water')

仅在构造函数里使用 readonly name: string 参数来创建和初始化 name 成员。 我们把声明和赋值合并至一处。 参数属性通过给构造函数参数前面添加一个访问限定符来声明。使用 private 限定一个参数属性会声明并初始化一个私有成员;对于 publicprotected 来说也是一样的

抽象类

抽象类作为其他派生类的基类使用。abstract关键字用于定义抽象类和在抽象类内部定义抽象方法

什么是抽象类

  • 抽象类不允许被实例化
js
abstract class Animal {
  public name;
  public constructor(name) {
    this.name = name;
  }
  public abstract sayHi();
}

let a = new Animal('Dog');

// index.ts(9,11): error TS2511: Cannot create an instance of the abstract class 'Animal'.


  • 抽象类中的抽象方法必须被子类实现
js
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');

// index.ts(9,7): error TS2515: Non-abstract class 'Cat' does not implement inherited abstract member 'sayHi' from class 'Animal'.


我们定义了一个类 Cat 继承了抽象类 Animal,但是没有实现抽象方法 sayHi,所以编译报错了。 下面是一个正确使用抽象类的例子:

js
abstract class Animal {
  public name;
  public constructor(name) {
    this.name = name;
  }
  public abstract sayHi();
}

class Cat extends Animal {
  public sayHi() {
    console.log(`Meow, My name is ${this.name}`);
  }
}

let cat = new Cat('Tom');


因为实现了抽象方法sayHi,所以编译通过了。

为函数定义类型

js中有两种常见的定义函数的方式(函数声明、函数表达式)

js
// 函数声明
function sum(x,y){
  return x + y
}

// 函数表达式
const mySum = function(x,y) {
  return x + y
}

ts中的函数声明

js
function sum(x:number,y:number):number {
  return x + y
}

定义类型之后,如果输入的参数不符合定义时候的类型和数量,这样编译是不会通过的

js
function sum(x: number, y: number): number {
    return x + y;
}
sum(1, 2, 3);

// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
function sum(x: number, y: number): number {
    return x + y;
}
sum(1);

// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.


函数表达式

js
const mySum = function (x:number,y:number):number{
  return x + y
}

其实上面例子可以编译通过其实还涉及到了类型推论,因为只定义了右侧的类型,左侧的是靠推断出来的,所以完整的定义如下

js
const mySum:(x:number,y:number) => number = function(x:number,y:number):number{
  return x + y
}

用接口定义函数类型

js
interface SearchFunc {
  (name:string,age:string):string
}

let mySearch:SearchFunc
mySearch = function(name:string,age:string){
  return name + age
}

可选参数和默认参数

js
function FullName(firstName:string,lastName?:string):string{
  if(lastName){
    return firstName + '-' + lastName
  }else {
    return firstName
  }
} 

这里有个需要注意的地方,可选参数必须接在必需参数后面,如果不准守这个规则会导致编译不通过

js
// 编译会报错
function FullName(firstName?:string,lastName:string){
   if(lastName){
    return firstName + '-' + lastName
  }else {
    return firstName
  }
}

可以为参数添加默认值

js
function FullName(firstName:string='a',lastName?:string):string {
  if(lastName){
    return firstName + '-' + lastName
  }else {
    return firstName
  }
}

剩余参数

可以把剩余的参数全部集中在一个变量中

js
function info(x:string,...args:string[]){
  console.log(x,args)
}
info('abc','a','b','c')

函数重载

所谓函数重载,即函数名相同, 而形成不同的多个同名函数。在 JavaScript 里, 由于弱类型的特点和形参与实参可以不匹配, 是没有函数重载这一说的。但在 TypeScript 里, 由于有了类型系统,所以就有了函数重载。

js
/* 
 * 函数重载: 函数名相同, 而形参不同的多个函数
 * 需求: 我们有一个add函数,它可以接收2个string类型的参数进行拼接,也
 * 可以接收2个number类型的参数进行相加 
*/

// 重载函数声明
function add(x: string, y: string): string;
function add(x: number, y: number): number;

// 定义函数实现
function add(x: string | number, y: string | number): string | number {
  // 在实现上我们要注意严格判断两个参数的类型是否相等,而不能简单的写一个 x + y
  if (typeof x === "string" && typeof y === "string") {
    return x + y;
  } else if (typeof x === "number" && typeof y === "number") {
    return x + y;
  }
}

console.log(add(1, 2));
console.log(add("a", "b"));
// console.log(add(1, 'a')) // error


范型

使用函数范型

js
function createArray<T>(value:T,count:number) {
  const arr: Array<T> = []
  for (let index = 0;index < count;index++){
    arr.push(value)
  }
  return arr
}

const arr = createArray<number>(11,3)
const arr1 = createArray<string>("aa",3)

多个泛型参数的函数

js
function func<K,V>(a:K,b:V):[K,V]{
   return [a,b]
}

const res = func<string,number>('a',1)

泛型接口

js
interface IBase<T> {
 data:T[]
 add: (t:T) => void
 get: (id:number) => T
}


class User {
 id?:number
 name:string
 age:number
 constructor(name,age){
   this.name = name
   this.age = age
 }
}

class UserWater implements IBase<User> {
 data:User[] = []

 add(user: User):void {
   user = {...user,id:Date.now()}
   this.data.push(user)
 }

 get(id:number):Uset {
   return this.data.find((item) => item.id === id)
 }
}


泛型类

在定义类时, 为类中的属性或方法定义泛型类型 在创建类的实例时, 再指定特定的泛型类型

js

class Water<T>{
 value: T
 add: (x:T,y:T) => T
}

let water = new Water<number>()
water.value = 1
water.add = function(x,y){
 return x + y
}

泛型约束

没有泛型约束

js
function fn<T>(x:T):void {

  // 不是所有的类型都有length属性
  console.log(x.length) // error
}


使用泛型约束解决

js
interface Water {
  length: number
}

// 指定泛型约束
function func<T extends Water>(x:T):void {
  console.log(x.length)
}