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源码,可以比较看看
1 var __extends = (this && this.__extends) || function (d, b) { 2 for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 3 function __() { this.constructor = d; } 4 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 5 }; 6 // 基类 7 var Animal = (function () { 8 function Animal(theName) { 9 this.name = theName; 10 } 11 Animal.prototype.eat = function () { 12 console.log(this.name + " \u5403\u98DF\u7269\u3002"); 13 }; 14 return Animal; 15 }()); 16 // 子类继承基类 17 var Dog = (function (_super) { 18 __extends(Dog, _super); 19 function Dog(theName) { 20 _super.call(this, theName); 21 } 22 Dog.prototype.eat = function () { 23 _super.prototype.eat.call(this); 24 console.log('并且吃的是狗粮。'); 25 }; 26 return Dog; 27 }(Animal)); 28 var People = (function (_super) { 29 __extends(People, _super); 30 function People(theName) { 31 _super.call(this, theName); 32 } 33 // 子类重写基类方法 34 People.prototype.eat = function () { 35 console.log(this.name + " \u62D2\u7EDD\u5403\u72D7\u7CAE\u3002"); 36 }; 37 return People; 38 }(Animal)); 39 var animal = new Animal('动物'); 40 animal.eat(); 41 var dog; 42 dog = new Dog('狗'); 43 dog.eat(); 44 var people; 45 people = new People('人类'); 46 people.eat();
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();
当继承链过深,代码需要在某一个子类的类型下执行时,这种方法比较有效。