本篇將介紹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源碼,可以比較看看
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();
當繼承鏈過深,代碼需要在某一個子類的類型下執行時,這種方法比較有效。