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();
當繼承鏈過深,程式碼需要在某一個子類的型別下執行時,這種方法比較有效。