TypeScript學習筆記(四) - 類和接口

本篇將介紹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();
複製代碼

當繼承鏈過深,代碼需要在某一個子類的類型下執行時,這種方法比較有效。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章