TypeScript的类 接口与泛型

面向对象

TypeScript 是面向对象类的编程。

什么是面向对象呢?

简而言之,就是程序中所有的操作,都是通过对象来完成的。计算机程序本质是对现实事物的抽象。一个人,一辆车,一只狗,这些都是对象,而这些对象发出的动作就是对象的方法,某些特征就是对象的属性。比如一个人,他的身高,长相,姓名,年龄等这些在对象中都是他的属性,而他发出的动作,走,跑,说话等这些在对象中都是他的方法。

类是什么?

类其实就是对象的模型,通过类可以来创建对象。对象的属性和方法,要在类中明示的表示出来。

下面定义一个简单的类:

class Person {
  name: string = 'xiaobai';
  static age: number = 18;
  say() {
    console.log('hello');
  }
  static run() {
    console.log('run');
  }
}
let person = new Person()

上面定义了一个简单的类,通过 new 关键字,实例化成对象。name 是实例对象。

static 指的是类的静态属性,用类名访问,无需创建对象,比如 Person.age。但是实例对象访问不到。静态方法与静态属性使用方式相同。

类的构造函数 constructor

为什么需要它呢?

当我们在创建类的时候,通常都不可能只实例化一个对象,通常会实例化多个对象。

// 这样创建出来的实例对象都是相同的属性值和方法
class Person {
  name: string = 'xiaobai';
  say() {
    console.log('hello');
  }
}
let person = new Person()

constructor 这个构造函数在每次 new 实例化对象,立即执行。

class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
    console.log(this);  // 它指向的是per这个实例化的对象
  }
}
let per1 = new Person('xiaobai1');
let per2 = new Person('xiaobai2');
let per3 = new Person('xiaobai3');

在每次实例化对象完成之后,在 constructor 函数里面会生成一个 this 这个 this 指向的就是实例化产生的对象。

通过this.name 添加属性,就相当于往实例化的对象里面添加属性。这样我们再把实例化时传的参数带进去,这样就会创建出不同属性值的对象了。方法的话,就是哪个实例对象调用方法,方法里面的this就指向谁。

类的继承 extends

class Dog {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  say() {
    console.log('汪汪');
  }
}

class Cat {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  say() {
    console.log('喵喵');
  }
}

我们分别创建一个 Dog 和 Cat 类,我们发现它们都有相同的属性和方法,这样的代码我们重复写了两遍,如果我们再去创建一个牛,蛇,猪等更多的类,这样就开始做重复的工作了。

因此我们使用继承来简化它:

class Animal {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  say() {
    console.log('叫');
  }
}
class Dog extends Animal {
  run() {
    console.log(`${this.name}在跑`);
  }
  // 重写父类方法
  say() {
    console.log('汪汪')
  }
}

class Cat extends Animal {
  // 重写父类方法
  say() {
    console.log('喵喵')
  }
}
let dog = new Dog('小白', 3);
console.log(dog);      // {name: "小白", age: 3}
dog.say();  // 汪汪
let cat = new Cat('小黑', 2);
console.log(cat);      // {name: "小黑", age: 2}
dog.say();  // 喵喵

从上面代码可以看出,我们使用了 extends 关键字来继承 Animal 这个类,从而使 DogCat 这两个类,都拥有了 Animal 类上的方法。这大大简化了我们的代码量,而且不用在去做重复的工作。

继承的子类,不仅有拥有父类的方法,还能有自己的方法,比如,子类 Dog 中的 run 方法。

如果继承的子类中添加了与父类相同的方法,子类的方法将覆盖父类的方法。这个子类方法覆盖掉父类的方法的形式,称为方法的重写

super 关键字

先写一段代码:

class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  say() {
    console.log('叫');
  }
}
class Dog extends Animal {
  age: number;
  constructor(name: string, age: number) {
    super(name);   // 调用父类的构造函数
    this.age = age;
  }
  say() {
    super.say()    // 调用父类的方法
  }
}
let dog = new Dog('小白', 3);
console.log(dog);

在子类的方法中使用 super 表示当前类的父类。

在子类的添加新的属性,如果在子类中写了新的构造函数,子类构造函数必须对父类构造函数进行调用。

抽象类

有的时候,我们并不需要父类来实例化创建对象,父类是一个超类,只用来存放公用的属性和方法,用来继承使用。这个时候就用到了抽象类,抽象类只能用来继承,而不能用来实例化创建对象。

abstract class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  abstract say(): void;
}
class Dog extends Animal {
  say() {
    console.log('叫');
  }
}

在 abstract 抽象类中,才能定义抽象方法,子类必须对抽象方法进行重写。抽象方法使用 abstract 开头,没有方法体。抽象类中,可以不写抽象方法。

类的修饰符

类的修饰符 有三个 public private protected

public

公共属性,修饰的属性可以在任意位置进行访问(修改),包括继承的子类

属性前不设置修饰符时 默认为 public

class Person {
  public name: string;
  public age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}
let person = new Person('孙悟空', 18);
console.log(person);    // 输出 {name: "孙悟空", age: 18}
person.name = '猪八戒';
person.age = 20;
console.log(person);    // 此时输出 {name: "猪八戒", age: 20}

此时可以看出使用 public 修饰属性:

属性可以在实例化对象中任意被修改,这样使对象中的数据变得非常不安全。

private

私有属性,只能在类内部进行访问(修改)

此时我们将 Person 类中的,name属性改成 私有属性,结果如下:
TypeScript的类 接口与泛型_第1张图片

这样属性被私有化,外部就不能 以对象点属性的方法去访问了,那么我们可以通过 向类中添加方法去访问(修改)里面的属性,如下:

class Person {
  private name: string;
  public age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  getName () {
    return this.name;
  }

  setName (value: string) {
    this.name = value;
  }
}
let person = new Person('孙悟空', 18);
person.setName('猪八戒');
console.log(person.getName());   // 打印出 '猪八戒'

可以通过设置 setName 方法,在对象内部改变去修改 name 这个私有属性。

当然也可以直接直接使用 TS 提供的 getter , setter 方法,代码如下:

class Person {
  private name: string;
  public age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  get _name() {
    return this.name;
  }

  set _name(value: string) {
    this.name = value;
  }
}
let person = new Person('孙悟空', 18);
person._name = '猪八戒';
console.log(person._name);   // '猪八戒'

private可以做属性的封装,不直接去访问它的属性,通过 getter,setter 方法去访问。

protected

受保护的属性,只能在当前类和子类中使用(修改),不能在实例化的对象中去访问。

接口

接口实现对象

接口用来定义一个类结构,用来定义一个类中应该哪些属性和方法,同时接口也可以当做类型声明去使用。

举个例子:

interface User {
  name: string;
  age: number;
}

interface User {
  gender: string;
}

const USER1: User = {
  name: '小明',
  age: 18,
  gender: '男'
}

console.log(USER1);

我们定义一个 user 对象的接口,声明的第一个接口中包括,name 和 number 属性,声明第二个接口包含 gender 属性,我们可以看到两个接口可以重复声明,并且两个接口中的属性检测会合并,我们再去使用这个接口时,就必须有 name age gender。

接口实现类

接口可以在定义类的时候去限制类的解构

接口定义类的特点:

  • 接口中所有的属性都不能有实际值
  • 接口只定义对象结构,而不考虑实际值
  • 在接口中所有的方法都是抽象方法

定义类时,可以使类去实现一个接口,实现接口就是使类满足接口的要求

/**
 * 接口实现类
 */
interface user {
  name: string;
  say(): void;
}

class user1 implements user {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  say() {
    console.log('hello');
  }
}

由此可以看出,接口很像抽象类,但是也有很显著的区别:

  • 在接口中的方法都是抽象方法,不能有实际的值,在抽象方法中可以有抽象方法,也可以有普通方法
  • 在使用接口的时候,使用关键字 implements 实现,在使用抽象类的时候,使用 extends 继承

本质上两者都是对类定义一个标准,使实现接口,或者继承抽象方法的类,必须去符合这个标准。

泛型

在定义函数或者类时,如果遇到类型不明确的就可以使用泛型。

当我们不明确函数传参是什么类型的时候,往往会使用 any 来表示任意类型,但是使用 any ,会关闭 TS 的类型检测,因此这里要用泛型来代替。

举个简单的代码案例:

// 使用any,关闭类型检测
function fn(a: any): any {
   return a;
}

// 使用泛型,自动判断类型
function fn<T> (a: T): T {
  return a;
}
fn<string>('hello');  // 也可以在调用的时候,自己指定类型

类使用泛型:

class User<T> {
  name: T;
  constructor(name: T) {
    this.name = name;
  }
}
let user = new User<string>('zhangsan');

any ,会关闭 TS 的类型检测,因此这里要用泛型来代替。

举个简单的代码案例:

// 使用any,关闭类型检测
function fn(a: any): any {
   return a;
}

// 使用泛型,自动判断类型
function fn<T> (a: T): T {
  return a;
}
fn<string>('hello');  // 也可以在调用的时候,自己指定类型

类使用泛型:

class User<T> {
  name: T;
  constructor(name: T) {
    this.name = name;
  }
}
let user = new User<string>('zhangsan');

你可能感兴趣的:(TS面向对象编程,typescript)