Classes & Interfaces
Create first Class
如何在 TypeScript 里创造类
class Department {
name: string;
constructor(n: string) {
this.name = n;
}
}
const accounting = new Department("Accounting");
console.log(accounting);
在这里我们指明了 name 的类型为 string,但是实际编译到 es6 的 JavaScript 里,是不会有这一行限制的。
class Department {
constructor(n) {
this.name = n;
}
}
Constructor Functions & The this Keyword
来看一下 this 关键词
class Department {
name: string;
constructor(n: string) {
this.name = n;
}
describe() {
console.log("Department: " + this.name);
}
}
const accounting = new Department("Accounting");
const accountingCopy = { describe: accounting.describe };
accountingCopy.describe();
如果运行这段代码,得到的结果会是什么?
Department: undefined
为什么是 undefined? 因为我们只是把 accounting 的这个方法的引用 copy 了过去,在实际运行的 accountingCopy 的 Object 里,并没有 name 这个属性。this 指向的,是运行这个函数的对象。
class Department {
name: string;
constructor(n: string) {
this.name = n;
}
describe(this: Department) {
console.log("Department: " + this.name);
}
}
const accounting = new Department("Accounting");
const accountingCopy = {
name: "DUMMY",
describe: accounting.describe,
};
accountingCopy.describe();
我们在这里给 describe 加上了一个参数,this,还给它限定了类型,为 Department。TypeScript 会分析这段代码,知道这个方法,应该什么样的 object 下才能被调用。
如果这里吧 accountingCopy 的 name 去掉,会出现错误,因为 TypeScript 知道它和 Department 类的应该有的属性不一样。
private and public Access Modifiers
TypeScript 提供了 public 和 private,这在 JavaScript 中没有。
class Department {
public name: string;
private employees: string[] = [];
constructor(n: string) {
this.name = n;
}
describe(this: Department) {
console.log("Department: " + this.name);
}
addEmployee(employee: string) {
this.employees.push(employee);
}
}
const accounting = new Department("Accounting");
// accounting.employees=[];
默认情况下,声明的变量都为 public,设置了 private 的变量,无法从外部直接访问 。
Shorthand Initialization
快速的初始化,从而摆脱重复的赋值
class Department {
private name: string;
private id: string;
constructor(id: string, name: string) {
this.name = name;
this.id = id;
}
}
如果我们初始化的值很多,这样不断地反复赋值会很幸苦,TypeScript 提供了更快捷的办法。
class Department {
// private name: string;
// private id: string;
constructor(private id: string, public name: string) {
// this.name = name;
// this.id = id;
}
}
直接在构造函数里一次性声明清楚 private 还是 public,会创建同名值并赋值。
Readonly
只读属性,当你确保一个属性不会再更改且不希望更改,使用 readonly
class Department {
constructor(private readonly id: string, public name: string) {}
}
Inheritance
class Department {
private employees: string[] = [];
constructor(private readonly id: string, public name: string) {}
describe(this: Department) {
console.log(`Department (${this.id}): ${this.name}`);
}
addEmployee(employee: string) {
this.employees.push(employee);
}
printEmployeeInformation() {
console.log(this.employees.length);
console.log(this.employees);
}
}
class ITDepartment extends Department {
admins: string[];
constructor(id: string, admins: string[]) {
super(id, "IT"); // Call the constructor function of the base Class at first
this.admins = admins;
}
}
TypeScript 的继承和 JavaScript 基本一样,使用 extends 关键字,同时调用 super 方法。
super()会在构建这个类之前先运行父类的 constructor。
同样也可以自己在新类中添加方法。
Overriding Properties & The protected Modifier
在继承类中可以重写父类的方法。
父类的属性设置为 protected 而不是 private,即可在子类做修改。
class Department {
protected employees: string[] = [];
constructor(private readonly id: string, public name: string) {}
addEmployee(employee: string) {
// validation etc
// this.id = 'd2';
this.employees.push(employee);
}
}
class ITDepartment extends Department {
constructor(id: string) {
super(id, "IT");
}
addEmployee(employee: string) {
this.employees.push(employee);
}
}
Getter & Setter
Getter 用于获取类中的特定属性,设定一个 getter 可以在获取时候内部做处理后再返回。
Setter 可以设置类中的属性。使用的时候就和直接设置属性值一样。也可以在内部处理。
class Department {
protected employees: string[] = [];
get reverseEmployees() {
return this.employees.reverse();
}
set setReverseEmployees(a: string[]) {
this.employees = a.reverse();
}
constructor(private readonly id: string, public name: string) {}
addEmployee(employee: string) {
// validation etc
// this.id = 'd2';
this.employees.push(employee);
}
}
const d = new Department("1", "ad");
console.log(d.reverseEmployees);
d.setReverseEmployees = ["1", "2"];
Static
类中有静态方法,他可以不用创造实例,直接调用,就像 Math 类的 PI,可以直接获取,或者 pow 方法,直接调用。
class Department {
static fiscialYear = 2020;
protected employees: string[] = [];
constructor(private readonly id: string, public name: string) {
// console.log(this.fiscialYear); Not available in a instance
console.log(Department.fiscialYear);
}
addEmployee(employee: string) {
this.employees.push(employee);
}
static createEmployee(name: string) {
return { name };
}
}
const pi = Math.PI;
const p = Math.pow(1, 2);
const employee = Department.createEmployee("Max");
console.log(Department.fiscialYear);
注意,在构造函数可以看到,如果要输出 fiscialYear 变量,不可以使用 this,因为 static 属性不属于任何一个实例。所以直接从 Department 调用就好。
Abstract Classes
当你想确保某种方法可用,而且对于每一个具体的,基于这个类继承的所有子类的这个方法的具体实现都不一样,也就是我们不能提供一个通用的方法的时候,但你想强制这个方法存在且要求子类实现,使用 abstract。
当你想强制所有基于其他类的类,分享一些常用的方法或属性,同时你不想提供具体的值或者实现,使用 abstract。
abstract class Department {
constructor(protected readonly id: string, public name: string) {}
abstract describe(this: Department): void;
}
在 class 关键字前添加 abstract,同时再要 abstract 的方法或者属性前也添加 abstract,不需要提供具体实现,只需要写清楚返回类型和参数类型即可。
注意:被 abstract 的类不可以再实例化,也就是无法创建一个新的基于 Department 的 Object。
class ITDepartment extends Department {
constructor(id: string, admins: string[]) {
super(id, "IT");
}
describe() {
console.log("IT Department - ID: " + this.id);
}
}
本例中,其扩展类必须补充完整 describe 方法,不然会报错。
Singletons & Private Constructor
当需要一个类只有一个具体的实例化对象时候,使用单例模式 Singletons
class Department {
static instance: Department;
private constructor(private readonly id: string, public name: string) {}
static getInstance() {
if (Department.instance) return this.instance;
this.instance = new Department("1", "n");
return this.instance;
}
}
const theOnlyInstance = Department.getInstance();
首先设置 constructor 为 private,使其无法从外直接调用。
随后建立一个 static 变量,instance。用于储存唯一的,实例化的对象。
最后添加静态方法,实例存在就返回它,不存在就新建一个再返回。
A First Interface
使用 Interface 去定义一个 Object 的结构,他应该拥有什么属性,每个属性又该有什么特点,同时也可以使用它来做类型检查。
interface Person {
name: string;
age: number;
greet(phrase: string): void;
}
let user1: Person;
user1 = {
name: "Jack",
age: 28,
greet(phrase: string) {
console.log(phrase);
},
};
Using Interface with Classes
我们已经有 type 了,为什么还需要 Interface?
type Person = {
name: string;
age: number;
greet(phrase: string): void;
};
有时候可以互换,但是 Interface 之可以用于描述对象的结构。当定义某些东西为接口的时候,Interface 会更加清晰的定义对象的结构。
interface Greetable {
name: string;
greet(phrase: string): void;
}
class Person implements Greetable {
name: string;
constructor(name: string) {
this.name = name;
}
greet(phrase: string) {
console.log(phrase);
}
}
let user: Greetable;
user = new Person("Jack");
我们定义了 Interface 后在 Class 使用,因为我们希望这个 Class 有这个结构,使用 implements 关键字。可以同时应用多个 Interface,逗号相连即可。
可以看到,user 的类型设置为 Greetable,但可以新机按一个 Person 实例,因为 Person 就是按照 Greetable 建的。
Interface 和 Abstract Class 有区别。Interface 内不能提供任何具体实现,Abstract Class 除了抽象部分方法外,内部可以写完整的函数功能。
Readonly Interface Properties
在 Interface 里设置一个属性为 readonly,在建 Class 时如果应用,那么这个属性会自动只读
interface Greetable {
readonly name: string;
}
class Person implements Greetable {
name: string;
constructor(name: string) {
this.name = name;
}
}
let user: Greetable;
user = new Person("Jack");
user.name = "Fan"; // !!ERROR!!
Extending Interface
interface Named {
readonly name: string;
}
interface Greetable extends Named {
greet(phrase: string): void;
}
如果我们确定一个新的 Interface 一定会拥有一部分和另一个 Interface 一样的内容,那可以做扩展。可以同时扩展多个,使用逗号隔开。
Interface as Function Type
也可以使用 Interface 作为函数类型。
// type AddFn = (a: number, b: number) => number;
interface AddFn {
(a: number, b: number): number;
}
let add: AddFn;
add = (a: number, b: number) => a + b;
注意和 type 的区别,最后用的是’:‘而不是’=>'。
Optional Parameters & Properties
通过’?'操作符来让一个属性变得可选,不一定要具备。其可以是函数,也可以是参数,也可以是属性变量。
interface Named {
readonly name?: string;
outputName?: string;
}
interface Greetable extends Named {
greet?(phrase: string): void;
}
class Person implements Greetable {
name?: string;
constructor(n?: string) {
if (n) this.name = n;
}
}
Compiling Interfaces to JavaScript
如果编译 TypeScript 文件,找到对应的 JavaScript 文件其实会发现 Interface 部分什么内容都没有被编译。JavaScript 并没有这个概念,这只是 TypeScript 给我们提供的工具而已。