第06章 类

136 阅读3分钟

一、概述

传统方法中,JavaScript 通过构造函数实现类的概念,通过原型链实现继承。在 ES6 中,我们迎来了 Class。TypeScript 除了实现了所有 ES6 中的类的功能以外,还添加了一些新的用法。

二、示例

1. 属性修饰符

修飾符描述
public修饰公共属性/方法,可以在任何位置访问,所有属性和方法默认为 puiblic
private修饰私有属性/方法,不能在声明它的类的外部访问。
protected修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的。
readonly只读属性,必须在声明时或构造函数里被初始化。
static静态属性(类属性/类方法)→ 通过类名调用

我们来看一组示例:

// -- 定义一个Person 类
class Person {
    /** 定义静态属性并直接赋值(TS会自动推断country类型为string) */
    static country = "中国";
    /** 定义一个公共属性(注意:public关键字可省略) */
    public name: string;
    /** 定义一个私有属性,构造实例时可以赋值,但是不能通过实例访问 */
    private job: string;
    /** 定义一个受保护的属性,只允许在Perosn及其子类中访问 */
    protected birth: string;
    /** 定义一个只读属性,只允许在构造时赋值 */
    readonly gender: number;
    /** 构造函数 */
    constructor(name: string, job: string, birth: string, gender: number) {
        this.name = name;
        this.job = job;
        this.birth = birth;
        this.gender = gender;
    }
}

const person = new Person("Li-HONGYAO", "前端工程师", "1993/07/16", 1);

console.log(Person.country); // 中国
console.log(person.name); // Li-HONGYAO
console.log(person.job); // Property 'job' is private and only accessible within class 'Person'.
console.log(person.birth); // Property 'birth' is protected and only accessible within class 'Person' and its subclasses.
 
person.gender = 0; // annot assign to 'gender' because it is a read-only property.

在 TypeScript 中,构造器允许简写:

class Person {
    constructor(public name: string,  private job: string, protected birth: string, readonly gender: number) {}
}

const person = new Person("Li-HONGYAO", "前端工程师", "1993/07/16", 1);

console.log(person.name); // Li-HONGYAO
console.log(person.job); // Property 'job' is private and only accessible within class 'Person'.
console.log(person.birth); // Property 'birth' is protected and only accessible within class 'Person' and its subclasses.
 
person.gender = 0; // annot assign to 'gender' because it is a read-only property.

2. 抽象类

abstract 用于定义抽象类和其中的抽象方法。什么是抽象类?

  • 首先,抽象类是不允许被实例化的

  • 其次,抽象类中的抽象方法必须被子类实现

// -- 定义一个抽象类
abstract class Person {
    constructor(name: string) {}
    /** 抽象属性,不用赋值,要求在子类中必须定义此属性并为其赋值 */
    abstract job: string;
    /** 抽象方法,不包含具体实现,要求在之类中必须实现此方法 */
    abstract sayHello(name: string): void;
    /** 非抽象方法,无需要求子类实现,但是子类可以重写此方法 */
    running() {
        console.log("I'm running!");
    }
}

class Teacher extends Person {
    // Non-abstract class 'Teacher' does not implement inherited abstract member 'job' from class 'Person'.
    // Non-abstract class 'Teacher' does not implement inherited abstract member 'sayHello' from class 'Person'.
}

class Student extends Person {
    /** 实现抽象属性 */
    job: string;
    name: string;
    constructor(name: string, job: string) {
        super(name);
        this.job = job;
        this.name = name;
    }
    /** 实现抽象方法 */
    sayHello(name: string) {
        console.log(`Hello, ${name}!`);
    }
    /** 重写抽象类方法 */
    running() {
        console.log("I'm studing!")
    }
}

const stu = new Student("Li-HONGYAO", '前端工程师');
console.log(stu.job); // 前端工程师
console.log(stu.name); // Li-HONGYAO

stu.sayHello("Li-HONGYAO"); // Hello, Li-HONGYAO!
stu.running(); // "I'm studing!" 

三、类与接口

接口(Interfaces)可以用于对「对象的形状(Shape)」进行描述,通常,通过接口可以约束一个类的形状,只需要让这个类实现这个接口即可。

1. 类实现接口

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

举例来说,人是一个类,学生是人的子类。学生具备姓名属性和说话的能力,我们可以给学生添加一个姓名属性和说话的方法。这时候如果有另一个类,老师,也有姓名属性和说话的能力,这个时候,就可以把姓名属性和说话的方法提取出来,作为一个接口,老师和学生都去实现它:

interface Person {
  name: string
  speack: () => void;
}

class Teacher implements Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  speack() {
    console.log("我是一名老师!");
  }
}
class Student implements Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  speack() {
    console.log("我是一名学生!");
  }
}

一个类可实现多个接口,中间以逗号 , 隔开:

interface Person {
    name: string,
    speack: () => void;
}
interface Teach {
    course: string;
}
class Teacher implements Person, Teach {
    name: string;
    course: string;
    constructor(name: string, course: string) {
        this.name = name;
        this.course = course;
    }
    speack() {
        console.log(`我是一名${this.course}老师!`);
    }
}

上述示例中,Teacher 类实现了Person 和 Teach 接口。

2. 接口继承类

class Point {
  x: number = 0;
  y: number = 0;
}
interface Point3d extends Point {
  z: number;
}

let point3d: Point3d = { x: 1, y: 2, z: 3 };

四、单例模式

接下来我们看看如何在 TypeScript 中定义一个单例:

class Singleton {
  name: string = '';
  private static instance: Singleton;
  private constructor() {}
  static defaultSingleton() {
    if(!this.instance) {
      this.instance = new Singleton();
    }
    return this.instance;
  }
}

const single1 = Singleton.defaultSingleton();
const single2 = Singleton.defaultSingleton();

console.log(single1 === single2); // true

single1.name = 'Muzili';
console.log(single2.name); // Muzili