TS 类

354 阅读6分钟

  • 类(Class):定义了一件事物的抽象特点,它将数据(属性)以及这些数据上的操作(方法)封装在一起。
  • 对象(Object):类的实例,通过 new classname生成
  • 类是抽象的,不占用内存,而对象是具体的,占用存储空间。

JavaScript 是基于对象的语言,而TypeScript是面向对象的语言。

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

类的定义

使用 class 定义类,类中声明类的成员(都是可选的)

  • 字段也就是属性,类中可包含0到多个
  • 构造函数 用constructor定义,类中可包含0到1个
  • 方法也就是行为动作,类中可包含0到多个
class Class_name {
	// 类体
        //构造方法
        constructor(){}
}

通过 new 类名生成新实例的时候,会自动调用类中构造方法constructor,可以给生成的实例对象添加属性。

类的属性和方法

直接定义的成员是实例成员,需要通过new创建的实例对象来访问实例成员。

class Car {
       //类的实例属性,通过new创建的实例对象来访问属性
    engine:string; //没有初始化
    price:number = 15;//初始化值为15
    name:string;
    age:number;
      constructor(name:string,age:number){
      //类中需要先定义好属性,才可以在构造方法中给实例对象添加属性
          this.name = name;
          this.age = age;
      }
    //类的实例方法
	disp():void {
		console.log(this.engine, this.price);
	}
	toGo(len:number):boolean {
		console.log(`走了${len}米远`);
		return true;
	}
  
	//字段式的方法,本质上这不是方法,而是属性
	m1 = (a:number, b:number):void=>{
		console.log("m1()")
	}
	m2:(a:number,b:number)=>void = (a, b)=>{
		console.log("m1()",a+b)
	}
	m3 = function():void {
		console.log("m3()")
	}
}
let car = new Car();
console.log(car.engine, car.price); 
car.disp();

声明属性时加类型声明以及初始化属性值都是是可选的; 类的方法参数和返回值都可声明类型,但都是可选的。

方法中可以使用this,该this指向调用方法的对象, 所以方法里可以使用 this.方法this.属性访问实例成员。

静态方法和静态属性

使用 static 修饰符修饰的方法和属性称为静态成员,它们不需要实例化,是类自己的属性和方法,实例对象是访问不到静态成员的。静态方法是直接通过类来调用,不能使用实例对象来访问静态成员。

class Animal {
  static isAnimal(a) {
    return a instanceof Animal;
  }
  static num = 42;

  constructor() {
    // ...
  }
}

let a = new Animal();
Animal.isAnimal(a); // true
a.isAnimal(a); // TypeError: a.isAnimal is not a function
console.log(Animal.num); // 42

构造函数和this

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

构造函数constructor中this表示当前使用new创建的实例

  • 主要用于初始化类的属性
  • 类的对象创建时自动调用执行
  • 没有返回值

实例方法中的this表示当前调用方法的对象

class Car {
    engine:string; 
    price:numbe;
    constructor(engine:string, price:number) {
		this.engine = engine;
		this.price = price;
	}
    printf():void { 
	   console.log(`engin=${this.engine}, 
           price=${this.price}`);
    }
}

总结:

  • 构造函数可带0到多个参数 (构造函数没有返值,也不用声明返回值类型)
  • 实例化对象
    • 构造函数无参时,new 类名();
    • 构造函数有参时,new 类名(为构造函数传实参)
  • 实例化过程(new过程)
    • 在堆中实例化对象,包括创建字段成员
    • 执行构造函数
    • 返回对象引用
  • 构造函数存在的价值
    • 初始化一些操作并为创建的对象属性赋值
    • 构造函数中this值为新实例化的对象引用,常通过"this.字段=值"为创建对象的字段赋值;
  • 方法中可以使用this,该this引用调用方法的对象, 所以方法里可以使用 this.方法this.字段访问实例成员;

类的继承

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

  • 子类继承父类后,子类的实例就拥有了父类中的所有属性和方法,继承的作用是增强代码的可复用性

  • 将子类共用的方法抽象出来放在父类中,自己特殊逻辑放在子类中重写父类的逻辑

  • 子类调用父类的方法,通过super

//父类
class Animal {
    name:string;
    age:number;
    constructor(name:string,age:number){
        this.name=name;
        this.age=age;
    }
    sayHellow(){
        console.log('动物在叫~');
    }
}
//子类
class Dog extends Animal{
//子类不需要再定义属性,会继承父类中的所有属性和方法
 constructor(name:string,age:number) {
    super(name,age); // 调用父类的构造方法constructor
    }
//可以调用父类的方法,还可以重写父类的方法
  sayHellow(){
      console.log('Dog类的sayHellow方法');
      //调用父类的方法
      super.sayHellow()
  }
}
//子类
class Cat extends Animal{

}

let dog=new Dog('youngmini',4);
let cat=new Cat('maomao',6);
console.log(dog);
dog.sayHellow();
console.log(cat);
cat.sayHellow();

image.png

class Animal {
    public name;
    constructor(name) {
        this.name = name;
    }
    sayHi() {
        return `My name is ${this.name}`;
    }
}

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

//类的继承
使用 extends 关键字实现继承,子类中使用 super 关键字来调用父类的构造函数和方法。
class Cat extends Animal {
  constructor(name) {
    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

只能单继承,一个类的父类只能有一个,但是一个类可以有多个子类。子类也可以再次被当作父类,形成树结构。

子类添加父类没有的成员和重写父类方法

class Animal {
    name:string;
    age:number;
    constructor(name:string,age:number){
        this.name=name;
        this.age=age;
    }
    sayHellow(){
        console.log('动物在叫~');
    }
}

class Dog extends Animal{
    run(){
        console.log(`${this.name}在跑`)
    }
}
class Cat extends Animal{

}
let dog=new Dog('youngmini',4);
let cat=new Cat('maomao',6);
console.log(dog);
dog.sayHellow();
console.log(cat);
cat.sayHellow();
dog.run();

image.png

如果在子类中添加了父类相同的方法,则子类中子类的方法会覆盖继承的父类方法,这就是子类重写继承的父类方法。

class Animal {
    name:string;
    age:number;
    constructor(name:string,age:number){
        this.name=name;
        this.age=age;
    }
    sayHellow(){
        console.log('动物在叫~');
    }
}

class Dog extends Animal{
    run(){
        console.log(`${this.name}在跑`)
    };
    sayHellow(): void {
        console.log('汪汪~~');
    }
}

class Cat extends Animal{
    sayHellow(): void {
        console.log('喵喵~~')
    }
}

let dog=new Dog('youngmini',4);
let cat=new Cat('maomao',6);
console.log(dog);
dog.sayHellow();
console.log(cat);
cat.sayHellow();
dog.run();

image.png

super关键字

class Animal {
    name:string;
    age:number;
    constructor(name:string,age:number){
        this.name=name;
        this.age=age;
    }
    sayHellow(){
        console.log('动物在叫~');
    }
}

class Dog extends Animal{
    kind:string;
    constructor(name:string,age:number,kind:string){
        // 调用父类的构造函数
        super(name,age);
        this.kind=kind;
    }
    run(){
        console.log(`${this.name}在跑`)
    };
    sayHellow(): void {
        console.log('汪汪~~');
    }
}


let dog=new Dog('youngmini',4,'犬科');
console.log(dog);
dog.sayHellow();
dog.run();

image.png

  • super 可以调用父类上的方法和属性(相当于ES5的语法,在静态方法和构造函数中指向父类; 在普通函数中指向父类的prototype;)
  • super(<实参>)要位于构造方法中的this使用之前
  • 子类对象的创建过程:创建父类对象,然后在父类对象基础上追加子类成员得到子类对象,即先声明子类的属性然后子类构造函数再执行,子类构造函数中super(<实参>)再调用父类构造函数的执行
  • 子类继承了父类构造函数必须使用super(<实参>),不管父类是否写了构造方法。没有继承父类,构造函数中就不能有super(<实参>)
  • 如果父类和子类都有构造函数,并且父类构造函数有参数,那么要求子类构造函数 super()中必须为父类传参数。

类的存取器

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

class Name {
    firstName:string;
    lastName:string;
    constructor(firstName:string,lastName:string){
        this.firstName = firstName;
        this.lastName = lastName;
    }
    // 读取器:用来读取数据
    get fullName(){
        //return this.firstName +  this.lastName;
        return 'jessica';
    }
    // 设置器:用来设置数据
    set fullName(value){
        console.log(value);
    }
}

image.png

类的修饰符

TypeScript 可以使用三种访问修饰符,分别是 public、private 和 protected。

  • public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 。
  • private 修饰的属性或方法是私有的,不能在声明它的类的外部访问,子类可以继承但是子类不能访问。
  • protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的。
class Animal {
  public name:string;
   private age:number;
  public constructor(name:string,age:number) {
    this.name = name;
    this.age = age;
  }
  public ageFun() {
  //类中可以访问
    console.log(this.age);
  }
}

let a = new Animal('jessica',18);
console.log(a.name); // jessica
a.name = 'Tom';
console.log(a.name); // Tom
let b = new Animal('hyomin',18);
//编译报错
console.log(b.age);

image.png

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

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

class Cat extends Animal {
  constructor(name:string) {
    super(name);
    // 编译报错
    console.log(this.name);
  }
}

image.png

用 protected 修饰,则允许在子类中访问:

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

class Cat extends Animal {
  constructor(name:string) {
    super(name);
    console.log(this.name);
  }
}

当构造函数修饰为 private 时,该类不允许被继承或者实例化:

class Animal {
  private name:string;
  public constructor(name:string) {
    this.name = name;
  }
}
// 编译报错
class Cat extends Animal {
  constructor(name:string) {
    super(name);
    console.log(this.name);
  }
}
// 编译报错
let a = new Animal('Jack');

image.png

image.png

构造函数修饰为 protected 时,该类只允许被继承,子类之外不能访问到:

class Animal {
    public name:string;
    protected constructor(name:string) {
        this.name = name;
    }
}
class Cat extends Animal {
    constructor(name:string) {
        super(name);
    }
}
// 编译报错
let a = new Animal('Jack');

image.png

参数属性和readonly

readonly只读属性关键字,但在构造方法中是可以修改的。

class X {
    readonly age:number;
    constructor(age:number){
        this.age = age;
    }
    update(){
        // 编译报错
        this.age = 15;
    }
}
const x = new X(18);
console.log(x)//X { age: 18 }
// 编译报错
x.age = 27;

image.png

image.png

readonly定义在参数上,会创建并且初始化的age参数。

class X {
    readonly age:number;
    constructor(readonly age:number){
        this.age = age;
    }
}

image.png

public、private、protected修饰符和readonly还可以使用在构造函数参数中,等同于类中定义该属性同时给该属性赋值,在构造函数中不再需要进行赋值使代码更简洁。

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

image.png

如果 readonly 和其他访问修饰符同时存在的话,需要写在其后面。

class Animal {
  // public readonly name;
  public constructor(public readonly name) {
    // this.name = name;
  }
}