TypeScript-类和接口的使用

1. 前言

前一篇文章介绍了TypeScript的入门:TypeScript数据类型,函数的声明和重载,所有本篇文章就来介绍一下TypeScript的高级编程,类的使用。

2. 类

在ES5中,要使用函数和基于原型的继承来创建可重用的组件,就必须理解TypeScript中的原型和原型链:Post not found: web开发/JavaScript:原型链和原型对象 JavaScript:原型链和原型对象,只有理解了JavaScript中的原型和原型链,才能基于原型的继承来创建可重用的组件。

而从从ECMAScript 2015,也就是ECMAScript 6开始,JavaScript程序员将能够使用基于类的面向对象的方式,虽然主流的浏览器已经支持ES6,但是如果想要兼容老版本浏览器,那就必须将ES6代码转换为ES5代码,虽然现在使用webpack这一点已经不用再过多的去操心。

而使用TypeScript中基于类的面向对象方式,编译后的JavaScript可以在所有主流浏览器和平台上运行。

在目前的主流框架中的模块化,则也是基于面向对象进行实现的。

下面是一个类的例子:

class Student {
  private name: string;
  private age: number;
  private sex: string;
  // 构造函数
  constructor(name: string, age: number, sex: string) {
    this.name = name;
    this.age = age;
    this.sex = sex;
  }
  show(): void {
    console.log(this.name + this.age + this.sex);
  }
}

var s = new Student("张三", 18, "男");
s.show();

2.1 面向对象三要素

在面向对象中有三要素:

  • 封装
  • 继承
  • 多态

这三要素在面试中偶尔会询问到。

2.2 封装

一种将抽象性函数接口的实现细节部分包装、隐藏起来的方法。同时,它也是一种防止外界调用端,去访问对象内部实现细节的手段,这个手段是由编程语言本身来提供的。

2.3 继承(Inheritance)

继承是面向对象中一个非常重要的概念,使用继承会大大的减少代码量,以及提高代码复用性。

例子:

如果有一个需求,需要管理一个班级上老师和学生的信息以及行为。

那么你应该怎么样去实现这个代码呢?

  1. 首先我们要分析老师和学生有哪些信息和行为。

    • 老师的信息和行为:姓名,性别,年龄,授课。
    • 学生的信息和行为:姓名,性别,年龄,学习。
  2. 假设老师和学生的信息和行为就分为上面4种,那么我们就需要通过代码进行实现。

    • 不通过继承进行实现。

    • 通过继承进行实现。

2.3.1 不通过继承进行实现

class Student {
  name: string;
  sex: string;
  age: number;
  constructor(name: string, sex: string, age: number) {
    this.name = name;
    this.sex = sex;
    this.age = age;
  }
  learn(): void {
    console.log(this.name + "在学习");
  }
}

class Teacher {
  name: string;
  sex: string;
  age: number;
  constructor(name: string, sex: string, age: number) {
    this.name = name;
    this.sex = sex;
    this.age = age;
  }
  teach(): void {
    console.log(this.name + "在授课");
  }
}

let s1: Student = new Student("张三", "男", 15);
s1.learn(); // 张三在学习

let teacher = new Teacher("王五", "男", 50);
teacher.teach(); // 王五在授课

观察上面的代码可以发现,在类的声明中出现了非常多的重复代码,比如:姓名,性别,年龄,这3项属性,老师和学生都拥有,但是在这里我们就写了2次。那么如果我们后期还需要添加一个”校长”类,那我们岂不是还需要再次重复写一遍这些代码?

为了解决上面的问题,我们就需要用到继承

2.3.2 通过继承进行实现

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

class Student extends Person {
  learn(): void {
    console.log(this.name + "在学习");
  }
}

class Teacher extends Person {
  teach(): void {
    console.log(this.name + "在授课");
  }
}

let s1: Student = new Student("张三", "男", 15);
s1.learn(); // 张三在学习

let teacher = new Teacher("王五", "男", 50);
teacher.teach(); // 王五在授课

在上面的代码中实际上编译器已经帮我们创建了构造函数。

class Student extends Person {
    constructor(name: string, sex: string, age: number) {
        super(name, age, sex);  //编译时默认会写
    }
}

可以看到,两种代码一对比,使用继承好像并没有减少太多的代码量,但是你如果想要新增一个“校长”类,继承的作用就会体验的淋漓尽致。

class Schoolmaster extends Person {
  management(): void {
    console.log(this.name + "在管理师生");
  }
}

2.4 抽象类

通过上面的例子后,我们可以看到Person这个类单纯是用来作为基类,而不需要进行实例化的,这个时候我们就可以将Person类设定为抽象类。

关键词:abstract

  • 提供其他类继承的基类,不能直接被实例化。
  • abstract关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。
  • 抽象方法只能放在抽象类里面。

声明抽象类只需要在class关键词前面加上abstract,即:

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

当然,抽象类是不能进行实例化的,如果你试图将抽象类进行实例化,那么编译器会报错。

image-20200811173751743

2.5 抽象方法

  • 抽象类中的抽象方法不包含具体实现但是必须在派生类中实现。
  • 抽象方法的语法与接口方法相似。 两者都是定义方法签名但不包含方法体。
  • 抽象方法必须包含 abstract关键字并且可以包含访问修饰符。

2.6 多态

父类定义的方法在子类中进行实现,每一个子类都有不同的表现。

class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  eat() {
    console.log("在吃肉");
  }
}

class Dog extends Animal {
  eat() {
    console.log(this.name + "在吃肉");
  }
  show() {
    console.log("名字叫做");
  }
}

let d1: Animal = new Dog("小白");
d1.eat(); // 小白在吃肉
d1.show(); // 报错

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法,当然,多态的用法还需要在项目中一步一步的进行理解。

不过我之前做前端项目的时候,类都很少用到,因为Vue,React这些已经帮你创建好类了。

2.7 静态方法和静态属性

静态方法和静态属性存在于类本身上面而并非在类的实例上。

一般用于一个类只有一个已经定义好的属性,比如圆周率,对于圆来说就是唯一的值,这个时候就可以申明为静态属性。

而对于一个类通用的方法,就可以声明为静态方法。

class Circle {
  r: number;
  constructor(r: number) {
    this.r = r;
  }
  static pi = 3.14;

  perimeter(): number {
    return 2 * Circle.pi * this.r;
  }
  static showPI(): number {
    return Circle.pi;
  }
}

注意:静态方法中只能调用静态属性

2.8 修饰符

在TypeScript里,成员都默认为 public,而除了public外,还有下面的几种修饰符。

  • public:(默认): 在类里面、子类、类外面都可以访问
  • protected:在类里面、子类里面可以访问,在类外部没法访问
  • private:在类里面可以访问,子类、类外部都没法访问
  • static:静态方法
  • readonly:将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。

3. 接口

3.1 作用

  • 在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范
  • 在程序设计里面,接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范
  • 接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要
  • typescrip中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类

3.2 属性类接口

对传入对象的约束

interface LabelledValue {
  label: string;
}

function printLabel(labelledObj: LabelledValue) {
  console.log(labelledObj.label);
}

let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);

如果在声明接口属性时添加?,则即为可选属性。

例如:

interface LabelledValue {
  label: string;
}

3.3 函数类型接口

对方法传入的参数以及返回值进行约束。

interface Encrypt {
  (key: string, value: string): string;
}

let md5: Encrypt = function (key: string, value: string): string {
  return key + value;
};

3.4 可索引接口

对数组的约束

interface UserArr {
  [index: number]: string;
}

let arr: UserArr = ["aaa", "bbb"];

对对象的约束

interface UserObj {
  [index: string]: string;
}

let arr: UserObj = { name: "张三" };

3.5 类类型的接口(重要)

类类型的接口:对类的约束,和抽象类有点相似。

implements

interface Animal {
  name: string;
  eat(): void;
}

class Dog implements Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  eat(): void {
    console.log(this.name + "吃食物");
  }
}

let d = new Dog("小白");
d.eat(); // 小白吃食物

3.6 接口扩展

接口可以继承接口。

interface Animal {
  eat(): void;
}

interface Person extends Animal {
  work(): void;
}

class Teacher implements Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  work(): void {
    console.log(this.name + "在工作");
  }
  eat(): void {
    console.log(this.name + "在吃饭");
  }
}

4. 最后

本篇文章只是起到抛砖引玉的作用,也是为了记录学习TypeScript的过程,所以很多细节都没有写到,如果需要系统学习,建议还是参照TypeScript文档,或者网上的学习视频。

总之,有了JavaScript的基础,TypeScript也仅仅是在JavaScript的基础上新加了一些东西,所以学习起来也并不困难。