TypeScript-类和接口的使用
1. 前言
前一篇文章介绍了TypeScript的入门:TypeScript数据类型,函数的声明和重载,所有本篇文章就来介绍一下TypeScript的高级编程,类的使用。
2. 类
在ES5中,要使用函数和基于原型的继承来创建可重用的组件,就必须理解TypeScript中的原型和原型链:JavaScript:原型链和原型对象,只有理解了JavaScript中的原型和原型链,才能基于原型的继承来创建可重用的组件。
而从从ECMAScript 2015,也就是ECMAScript 6开始,JavaScript程序员将能够使用基于类的面向对象的方式,虽然主流的浏览器已经支持ES6,但是如果想要兼容老版本浏览器,那就必须将ES6代码转换为ES5代码,虽然现在使用webpack这一点已经不用再过多的去操心。
而使用TypeScript中基于类的面向对象方式,编译后的JavaScript可以在所有主流浏览器和平台上运行。
在目前的主流框架中的模块化,则也是基于面向对象进行实现的。
下面是一个类的例子:
class Student { private name: string; private age: number; private sex: string; // 构造函数 constructor(name: string, age: number, sex: string) { this.name = name; this.age = age; this.sex = sex; } show(): void { console.log(this.name + this.age + this.sex); }}
var s = new Student("张三", 18, "男");s.show();
2.1 面向对象三要素
在面向对象中有三要素:
- 封装
- 继承
- 多态
这三要素在面试中偶尔会询问到。
2.2 封装
一种将抽象性函数接口的实现细节部分包装、隐藏起来的方法。同时,它也是一种防止外界调用端,去访问对象内部实现细节的手段,这个手段是由编程语言本身来提供的。
2.3 继承(Inheritance)
继承是面向对象中一个非常重要的概念,使用继承会大大的减少代码量,以及提高代码复用性。
例子:
如果有一个需求,需要管理一个班级上老师和学生的信息以及行为。
那么你应该怎么样去实现这个代码呢?
-
首先我们要分析老师和学生有哪些信息和行为。
- 老师的信息和行为:姓名,性别,年龄,授课。
- 学生的信息和行为:姓名,性别,年龄,学习。
-
假设老师和学生的信息和行为就分为上面4种,那么我们就需要通过代码进行实现。
-
不通过继承进行实现。
-
通过继承进行实现。
-
2.3.1 不通过继承进行实现
class Student { name: string; sex: string; age: number; constructor(name: string, sex: string, age: number) { this.name = name; this.sex = sex; this.age = age; } learn(): void { console.log(this.name + "在学习"); }}
class Teacher { name: string; sex: string; age: number; constructor(name: string, sex: string, age: number) { this.name = name; this.sex = sex; this.age = age; } teach(): void { console.log(this.name + "在授课"); }}
let s1: Student = new Student("张三", "男", 15);s1.learn(); // 张三在学习
let teacher = new Teacher("王五", "男", 50);teacher.teach(); // 王五在授课
观察上面的代码可以发现,在类的声明中出现了非常多的重复代码,比如:姓名,性别,年龄,这3项属性,老师和学生都拥有,但是在这里我们就写了2次。那么如果我们后期还需要添加一个”校长”类,那我们岂不是还需要再次重复写一遍这些代码?
为了解决上面的问题,我们就需要用到继承。
2.3.2 通过继承进行实现
class Person { name: string; sex: string; age: number; constructor(name: string, sex: string, age: number) { this.name = name; this.sex = sex; this.age = age; }}
class Student extends Person { learn(): void { console.log(this.name + "在学习"); }}
class Teacher extends Person { teach(): void { console.log(this.name + "在授课"); }}
let s1: Student = new Student("张三", "男", 15);s1.learn(); // 张三在学习
let teacher = new Teacher("王五", "男", 50);teacher.teach(); // 王五在授课
在上面的代码中实际上编译器已经帮我们创建了构造函数。
class Student extends Person { constructor(name: string, sex: string, age: number) { super(name, age, sex); //编译时默认会写 }}
可以看到,两种代码一对比,使用继承好像并没有减少太多的代码量,但是你如果想要新增一个“校长”类,继承的作用就会体验的淋漓尽致。
class Schoolmaster extends Person { management(): void { console.log(this.name + "在管理师生"); }}
2.4 抽象类
通过上面的例子后,我们可以看到Person
这个类单纯是用来作为基类,而不需要进行实例化的,这个时候我们就可以将Person
类设定为抽象类。
关键词:abstract
。
- 提供其他类继承的基类,不能直接被实例化。
- 用
abstract
关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。 - 抽象方法只能放在抽象类里面。
声明抽象类只需要在class
关键词前面加上abstract
,即:
abstract class Person { name: string; sex: string; age: number; constructor(name: string, sex: string, age: number) { this.name = name; this.sex = sex; this.age = age; }}
当然,抽象类是不能进行实例化的,如果你试图将抽象类进行实例化,那么编译器会报错。
2.5 抽象方法
- 抽象类中的抽象方法不包含具体实现但是必须在派生类中实现。
- 抽象方法的语法与接口方法相似。 两者都是定义方法签名但不包含方法体。
- 抽象方法必须包含
abstract
关键字并且可以包含访问修饰符。
2.6 多态
父类定义的方法在子类中进行实现,每一个子类都有不同的表现。
class Animal { name: string; constructor(name: string) { this.name = name; } eat() { console.log("在吃肉"); }}
class Dog extends Animal { eat() { console.log(this.name + "在吃肉"); } show() { console.log("名字叫做"); }}
let d1: Animal = new Dog("小白");d1.eat(); // 小白在吃肉d1.show(); // 报错
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法,当然,多态的用法还需要在项目中一步一步的进行理解。
不过我之前做前端项目的时候,类都很少用到,因为Vue,React这些已经帮你创建好类了。
2.7 静态方法和静态属性
静态方法和静态属性存在于类本身上面而并非在类的实例上。
一般用于一个类只有一个已经定义好的属性,比如圆周率,对于圆来说就是唯一的值,这个时候就可以申明为静态属性。
而对于一个类通用的方法,就可以声明为静态方法。
class Circle { r: number; constructor(r: number) { this.r = r; } static pi = 3.14;
perimeter(): number { return 2 * Circle.pi * this.r; } static showPI(): number { return Circle.pi; }}
注意:静态方法中只能调用静态属性。
2.8 修饰符
在TypeScript里,成员都默认为 public
,而除了public
外,还有下面的几种修饰符。
public
:(默认): 在类里面、子类、类外面都可以访问protected
:在类里面、子类里面可以访问,在类外部没法访问private
:在类里面可以访问,子类、类外部都没法访问static
:静态方法readonly
:将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。
3. 接口
3.1 作用
- 在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范
- 在程序设计里面,接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范
- 接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要
- typescrip中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类
3.2 属性类接口
对传入对象的约束
interface LabelledValue { label: string;}
function printLabel(labelledObj: LabelledValue) { console.log(labelledObj.label);}
let myObj = { size: 10, label: "Size 10 Object" };printLabel(myObj);
如果在声明接口属性时添加?
,则即为可选属性。
例如:
interface LabelledValue { label: string;}
3.3 函数类型接口
对方法传入的参数以及返回值进行约束。
interface Encrypt { (key: string, value: string): string;}
let md5: Encrypt = function (key: string, value: string): string { return key + value;};
3.4 可索引接口
对数组的约束
interface UserArr { [index: number]: string;}
let arr: UserArr = ["aaa", "bbb"];
对对象的约束
interface UserObj { [index: string]: string;}
let arr: UserObj = { name: "张三" };
3.5 类类型的接口(重要)
类类型的接口:对类的约束,和抽象类有点相似。
implements
interface Animal { name: string; eat(): void;}
class Dog implements Animal { name: string; constructor(name: string) { this.name = name; } eat(): void { console.log(this.name + "吃食物"); }}
let d = new Dog("小白");d.eat(); // 小白吃食物
3.6 接口扩展
接口可以继承接口。
interface Animal { eat(): void;}
interface Person extends Animal { work(): void;}
class Teacher implements Person { name: string; constructor(name: string) { this.name = name; } work(): void { console.log(this.name + "在工作"); } eat(): void { console.log(this.name + "在吃饭"); }}
4. 最后
本篇文章只是起到抛砖引玉的作用,也是为了记录学习TypeScript的过程,所以很多细节都没有写到,如果需要系统学习,建议还是参照TypeScript文档,或者网上的学习视频。
总之,有了JavaScript的基础,TypeScript也仅仅是在JavaScript的基础上新加了一些东西,所以学习起来也并不困难。