类和接口
本篇将介绍TypeScript里的类和接口。
与其他强类型语言类似,TypeScript遵循ECMAScript 2015标准,支持class类型,同时也增加支持interface类型。
一、类(class)
下面是一个类的基本定义方式:
1 class User {
2 name: string;
3 constructor(_name: string) {
4 this.name = _name;
5 }
6
7 sayHello(): string {
8 return `Hello,${this.name}!`;
9 }
10 }
11
12 let user = new User('John Reese');
13 user.sayHello();
在上面的例子里,定义了一个类User,这个类拥有一个属性、一个构造函数和一个实例方法sayHello。通过new的方式,可以用这个类实例化一个实例对象,并可以调用实例方法。这与大多数静态语言的声明方式一致。
1. 类成员的访问级别
与强类型语言类似,TypeScript的类成员可以显式声明访问级别:public、protected、private
1 class User {
2 name: string;
3 private sex: string;
4 protected age: number;
5 constructor(_name: string) {
6 this.name = _name;
7 }
8
9 sayHello(): string {
10 return `Hello,${this.name}!`;
11 }
12 }
13
14 let user = new User('John Reese');
15 user.name = 'Root'; // 公有属性,可以赋值
16 user.sex = 'female'; // 私有属性,无法赋值
17 user.age = 28; // 受保护属性,无法赋值
18 user.sayHello();
在TypeScript里,如果不显示指定访问级别,则默认为public。
2. 属性的get和set访问器
1 class User {
2 private _name: string;
3
4 get name(): string {
5 return this._name;
6 }
7
8 set name(newName: string) {
9 this._name = newName;
10 }
11
12 constructor(_name: string) {
13 this.name = _name;
14 }
15
16 sayHello(): string {
17 return `Hello,${this._name}!`;
18 }
19 }
20
21 let user = new User('John Reese');
22 user.name = 'Root';
23 user.sayHello();
通过get和set关键字声明属性访问器,通过属性访问器可以精确控制属性的赋值和获取值。下面是经过编译后生成的JavaScript代码
1 var User = (function () {
2 function User(_name) {
3 this.name = _name;
4 }
5 Object.defineProperty(User.prototype, "name", {
6 get: function () {
7 return this._name;
8 },
9 set: function (newName) {
10 this._name = newName;
11 },
12 enumerable: true,
13 configurable: true
14 });
15 User.prototype.sayHello = function () {
16 return "Hello," + this._name + "! " + this._age;
17 };
18 return User;
19 }());
20 var user = new User('John Reese');
21 user.name = 'Root';
22 user.sayHello();
3. 静态属性
静态属性即是通过类型而不是实例就可以访问的属性
1 class User {
2 static sex_type = ['male', 'female']; // 静态属性
3 name: string;
4 sex: string;
5
6 constructor(_name: string) {
7 this.name = _name;
8 }
9
10 sayHello(): string {
11 return `Hello,${this.name}!`;
12 }
13 }
14
15 let user = new User('John Reese');
16 user.name = 'Root';
17 user.sex = User.sex_type[1];
18 user.sayHello();
通过static关键字可以声明类型的静态属性。
4. 类的继承
同强类型语言一样,TypeScript也支持类的继承
1 // 基类
2 class Animal {
3 name: string;
4
5 constructor(theName: string) {
6 this.name = theName;
7 }
8
9 eat() {
10 console.log(`${this.name} 吃食物。`);
11 }
12 }
13
14 // 子类继承基类
15 class Dog extends Animal {
16 constructor(theName: string) {
17 super(theName);
18 }
19
20 eat() {
21 super.eat();
22 console.log('并且吃的是狗粮。');
23 }
24 }
25
26 class People extends Animal {
27 constructor(theName: string) {
28 super(theName);
29 }
30
31 // 子类重写基类方法
32 eat() {
33 console.log(`${this.name} 拒绝吃狗粮。`);
34 }
35 }
36
37 let animal = new Animal('动物');
38 animal.eat();
39
40 let dog: Animal;
41 dog = new Dog('狗');
42 dog.eat();
43
44 let people: Animal;
45 people = new People('人类');
46 people.eat();
从上面的例子可以看到,子类通过extends关键字可以继承其他类,通过super方法调用基类对应的方法,也可以直接重写基类的方法。
下面是编译之后生成JavaScript源码,可以比较看看
编译之后的JavaScript源码5. 抽象类
将上面的例子稍微修改下
1 // 抽象类
2 abstract class Animal {
3 name: string;
4
5 constructor(theName: string) {
6 this.name = theName;
7 }
8
9 abstract eat();
10 }
11
12 // 子类继承抽象类
13 class Dog extends Animal {
14 constructor(theName: string) {
15 super(theName);
16 }
17
18 eat() {
19 console.log(`${this.name} 吃狗粮。`);
20 }
21 }
22
23 let animal = new Animal('动物'); // 抽象类无法实例化
24 animal.eat();
25
26 let dog: Animal;
27 dog = new Dog('狗');
28 dog.eat();
通过abstract关键字声明抽象类和抽象方法,子类继承抽象类后,需要实现抽象方法。同样的,抽象类不能被实例化。
二、接口
下面是一个简单的接口声明
1 interface Animal {
2 name: string;
3 }
在JavaScript里没有对应的类型与之对应,所以编译之后不会生成任何JavaScript代码。
1. 作为参数类型
接口类型可以作为方法的参数类型,效果等同于直接指定Json对象的类型。
1 interface Animal {
2 name: string;
3 }
4
5 let printName = function(param: Animal) {
6 console.log(`Name is ${param.name}`);
7 }
8
9 printName({name: 'Dog'});
同样,接口成员也可以是缺省的
1 interface Animal {
2 name: string;
3 age?: number;
4 }
5
6 let printName = function (param: Animal) {
7 if (param.age) {
8 console.log(`Name is ${param.name}, and age is ${param.age}`);
9 } else {
10 console.log(`Name is ${param.name}`);
11 }
12 }
13
14 printName({ name: 'Dog' });
15 printName({ name: 'Dog', age: 5 });
但是在某些情况下,调用方法时,参数赋值可能会有多个,接口在作为参数类型时也支持拥有多个成员的情况。
1 interface Animal {
2 name: string;
3 age?: number;
4 [propName: string]: any; // 其他成员
5 }
6
7 let printName = function (param: Animal) {
8 if (param.age) {
9 console.log(`Name is ${param.name}, and age is ${param.age}`);
10 } else {
11 console.log(`Name is ${param.name}`);
12 }
13 }
14
15 printName({ name: 'Dog' });
16 printName({ name: 'Dog', age: 5 });
17 printName({ name: 'Dog', age: 5, character: '粘人' }); // 多于明确定义的属性个数
2. 作为其他类型
接口也可以定义方法的类型,和数组类型
1 interface FuncType {
2 (x: string, y: string): string; // 声明方法成员
3 }
4
5 let func1: FuncType;
6 func1 = function (prop1: string, prop2: string): string { // 方法参数名称不需要与接口成员的参数名称保持一致
7 return prop1 + ' ' + prop2;
8 }
9
10 interface ArrayType {
11 [index: number]: string; // 声明数组成员
12 }
13
14 let arr: ArrayType;
15 arr = ['Dog', 'Cat'];
3. 接口的继承与实现
同强类型语言一样,TypeScript的接口支持继承与实现。
1 interface Animal {
2 name: string;
3 eat(): void;
4 }
5
6 class Dog implements Animal {
7 name: string;
8 constructor(theName: string) {
9 this.name = theName;
10 }
11
12 eat() {
13 console.log(`${this.name} 吃狗粮。`)
14 }
15 }
16
17 class Cat implements Animal {
18 name: string;
19 constructor(theName: string) {
20 this.name = theName;
21 }
22
23 eat() {
24 console.log(`${this.name} 吃猫粮。`)
25 }
26 }
27
28 let dog: Animal;
29 dog = new Dog('狗狗');
30 dog.eat();
31
32 let cat: Animal;
33 cat = new Cat('喵星人');
34 cat.eat();
类通过implements关键字继承接口,并实现接口成员。
同时,接口也可以多重继承。
1 interface Animal {
2 name: string;
3 eat(): void;
4 }
5
6 interface Person extends Animal { // 继承自Animal接口
7 use(): void;
8 }
9
10 class People implements Person {
11 name: string;
12 constructor(theName: string) {
13 this.name = theName;
14 }
15
16 eat() {
17 console.log(`${this.name} 拒绝吃狗粮。`)
18 }
19
20 use() {
21 console.log(`${this.name} 会使用工具。`)
22 }
23 }
24
25 let man: Person;
26 man = new People('男人');
27 man.eat();
28 man.use();
4. 类型转换
在TypeScript里,接口可以对符合任一成员类型的对象进行转换,转换之后的对象自动继承了接口的其他成员。
1 interface Animal {
2 name: string;
3 age: number;
4 eat(): void;
5 }
6
7 let thing = { name: '桌子' };
8 let otherThing = <Animal>thing; // 类型转换
9 otherThing.age = 5;
10 otherThing.eat = function () {
11 console.log(`${this.name} 不知道吃什么。`)
12 };
上面的例子里,声明了拥有name属性的json对象,通过<>将json对象转换成了Animal类型的对象。转换后的对象则拥有了另外的age属性和eat方法。
5. 接口继承类
在TypeScript里,接口可以继承类,这样接口就具有了类里的所有成员,同时这个接口只能引用这个类或者它的子类的实例。
1 class People {
2 name: string;
3 private age: number;
4 constructor(theName: string) {
5 this.name = theName;
6 }
7
8 eat() {
9 console.log(`${this.name} 拒绝吃狗粮。`);
10 }
11
12 use() {
13 console.log(`${this.name} 会使用工具。`)
14 }
15 }
16
17 interface Animal extends People { // 接口
18
19 }
20
21 class Man extends People { // 子类
22
23 }
24
25 class Cat { // 拥有同样结构的另外一个类
26 name: string;
27 private age: number;
28 constructor(theName: string) {
29 this.name = theName;
30 }
31
32 eat() {
33 // 具体实现
34 }
35
36 use() {
37 // 具体实现
38 }
39 }
40
41 let cat: Animal;
42 cat = new Cat('喵星人'); // Cat类不是People的子类,无法被Animal引用
43
44 let man: Animal;
45 man = new Man('男人');
46 man.eat();
当继承链过深,代码需要在某一个子类的类型下执行时,这种方法比较有效。

浙公网安备 33010602011771号