1. 前言
前一篇文章介绍了TypeScript的入门:TypeScript数据类型,函数的声明和重载,所有本篇文章就来介绍一下TypeScript的高级编程,类的使用。
2. 类
在ES5中,要使用函数和基于原型的继承来创建可重用的组件,就必须理解TypeScript中的原型和原型链:Post not found: web开发/JavaScript:原型链和原型对象 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的基础上新加了一些东西,所以学习起来也并不困难。