TS 小记(9)TypeScript 类

72 阅读5分钟

概述

早期的 JavaScript 使用函数和基于原型的继承来创建「可重用组件」,但对于 Java 等面向对象编程语言开发者而言,这种方式有些认知差异,因为在 Java 等语言中,是基于类的继承并且由类来创建对象。从 ES6 开始,JavaScript 开始支持 这个面向对象编程特性。而在 TypeScript 中,默认就支持面向对象,编译后的 JavaScript 代码可以在所有主流浏览器和平台上运行,不需要 JavaScript 的下个版本。

TypeScript 中的类和 Java 等语言非常相似,有面向对象编程经历的开发者很快就能熟悉并上手。

类的语法

下面来学习下 TypeScript 中类的具体语法。

TypeScript 中使用 class 关键字定义一个类,后面则是类名,跟在最后是一对花括号,花括号中就是类的作用域:

class 类名 {
  
}

在类的作用域中包含以下几个元素:

  • 成员变量(属性/字段):类里声明的变量,表示对象的相关属性。
  • 成员方法:对象要执行的操作。
  • 构造函数:用来实例化一个类的对象,为对象分配内存。

实例化一个类的对象时,使用 new 关键字。

var/let/const 对象名 = new 类名([参数列表]);
// 比如
let xiaoming = new Person("xiaoming");

访问类中的属性和方法可以使用 .

对象名.属性名
对象名.方法名()
// 比如
xiaoming.name = "xiaoming";
xiaoming.sayHello();

看一个完整的简单例子:

class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    sayHello() {
        console.log("Hello, my name is " + this.name);
    }
}

let xiaoming = new Person("xiaoming");
xiaoming.sayHello();  // 输出:Hello, my name is xiaoming

在类中定义属性,前面不用加 varlet 关键字;

在类中定义方法,前面不用加 function 关键字。

上面代码展现了类的典型用法,以及成员变量,成员方法、构造函数等特性,它编译成 JavaScript 后是这样的:

var Person = /** @class */ (function () {
    function Person(name) {
        this.name = name;
    }
    Person.prototype.sayHello = function () {
        console.log("Hello, my name is" + this.name);
    };
    return Person;
}());
var xiaoming = new Person("xiaoming");
xiaoming.sayHello();

对于 Java 程序员来说一下子就懵逼了。这都是啥啥啥。。。

访问控制修饰符

TypeScript 中,可以给类的属性、方法、构造函数加上访问控制修饰符来控制其他类是否能访问这些元素,它是面向对象封装特性的体现。TypeScript 支持 3 种不同的访问控制修饰符。

  • public:公开的,默认修饰符,不写就是 public,可以在任何地方被访问。
  • protected:受保护的,可以在类自身和它的子类中访问。
  • private:私有的,只能在这个类自己内部访问,子类也无法访问。

例如:

class Person {
  name: string;           // public
  protected age: number;  // protected
  private height: number; // private
}

let person = Person();
person.name = "xiaoming"; // OK
person.age = 12;          // Error: 属性“age”受保护,只能在类“Person”及其子类中访问。
person.height = 140;      // Error: 属性“height”为私有属性,只能在类“Person”中访问。

类的继承

在面向对象编程中,一个类可以继承另一个类,继承的类称为子类(派生类),被继承的类称为父类(基类)。在 TypeScript 中,类继承关系用 extends 关键字表示。

class 子类 extends 父类 {}

// 比如
class Person {}
class Student extends Person {}
  • 子类中可以定义自己的属性和方法,还可以访问父类中非 private 的属性和方法

例如,我们有前面定义的 Person 类作为父类。再定义一个 Student 作为子类。

class Student extends Person {
    score: number;
}

let xiaoming = new Student("xiaoming");  
console.log(xiaoming.name);    // 输出:xiaoming
console.log(xiaoming.score);   // 输出:undefined
xiaoming.sayHello();           // 输出:Hello, my name is xiaoming
  • 子类中可以对父类方法实现进行覆盖。使用 super 关键字引用父类对象。
class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    sayHello() {
        console.log("Hello, my name is " + this.name);
    }
}

class Student extends Person {
    score: number;

    sayHello() {
      super.sayHello();
      console.log("My score is " + this.score);
    }
}

let xiaoming = new Student("xiaoming"); 
xiaoming.score = 90;
xiaoming.sayHello();           
// 输出:
Hello, my name is xiaoming
My score is 90

继承的代码编译成 JavaScript 后是这样:

var Person = /** @class */ (function () {
    function Person(name) {
        this.name = name;
    }
    Person.prototype.sayHello = function () {
        console.log("Hello, my name is " + this.name);
    };
    return Person;
}());
var Student = /** @class */ (function (_super) {
    __extends(Student, _super);
    function Student() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    Student.prototype.sayHello = function () {
        _super.prototype.sayHello.call(this);
        console.log("My score is " + this.score);
    };
    return Student;
}(Person));
  • TypeScript 不支持继承多个父类,但可以多重继承,可以使用 implements 关键字实现多个接口。
class Person {}

class Student extends Person {}

interface Play {
  likedSports: string[];
  doSports(): void;
}

class HighSchoolStudent extends Student implements Play {

  likedSports: string[];
  
  doSports(): void {
    console.log("Do sports");
  }
}

上面代码中,HighSchoolStudent 继承自 StudentStudent 继承自 Person,这是多重继承,同时 HighSchoolStudent 实现了 Play 接口。

static 关键字

static 关键字用来定义类级别的静态的成员,包括属性和方法,静态成员可以用 类名.静态成员 的方式直接访问。

class StaticMember {
  static num: number;
  static printNum(): void {
    console.log("num = " + StaticMember.num)
  }
}

StaticMember.num = 10;
StaticMember.printNum();

instanceof 关键字

可以使用 instanceof 运算符判断对象是否是指定的类型,如果是,返回 true,如果不是,返回 false。

class Person {
  name: string;
  constructor(name: string) {
      this.name = name;
  }
  sayHello() {
      console.log("Hello, my name is " + this.name);
  }
}

let person = new Person("owen");
console.log("typeof person = " + (typeof person));
console.log("person instanceof Person = " + (person instanceof Person));
// 运行结果
typeof person = object
person instanceof Person = true

抽象类

抽象类是作为其他子类的父类,约束子类一定要有某些属性或方法,它一般不会直接实例化。和接口不同,抽象类可以包含具体实现。使用 abstract 关键字定义抽象类及抽象类中的抽象方法。抽象类中的抽象方法没有实现,必须要在子类中实现。abstract 关键字不能和 private 关键字一起使用。

abstract class Person {
  
  abstract name: string;  // 抽象属性,和接口的属性一致,需要在子类中再次声明
  
  sayHello() {            // 普通函数,抽象类也可以有包含具体实现的普通函数
      console.log("Hello, my name is " + this.name);
  }

  abstract walk(): void;   // 抽象方法,必须要在子类中实现
}

class Student extends Person {
  
  name: string;

  constructor(name: string, score: number) {
    super();             // 子类必须要调用抽象类的构造函数
    this.name = name;
    this.score = score;
  }

  walk(): void {         // 子类实现父类的抽象方法
    console.log("Student walk");
  }
  
  score: number;

  sayHello() {
    super.sayHello();
    console.log("My score is " + this.score);
  }
}

let xiaoming = new Student("xiaoming", 90);
xiaoming.sayHello();
xiaoming.walk();
// 运行结果
Hello, my name is xiaoming
My score is 90
Student walk

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 9 天,点击查看活动详情