TS>ES6+
我们知道TS是JS的超级,也就是说不管是ES多少的版本,它里面的功能在TS里都有。比如let
,const
,箭头函数…… 这些和ES6重合的部分我之前有写过(也可能是翻译)一些文章:
Classes-类
我们先从新建一个类开始:
class School {
name: string;
constructor(n: string) {
this.name = n;
}
}
这个类叫做School
,代表一个「学校」类,里面的name
称为类的属性,constructor
我们称之为方法,而且这还是一个构造方法,构造方法在我们根据这个类新建对象的时候就会自动调用。现在我们来新建几个对象:
let school1 = new School('学校1');
let school2 = new School('学校2');
let school3 = new School('学校3');
console.log(school1, school2, school3);
然后来看一下打印结果:
我们新建的三个「学校」都打印出来了,下面我们再给这个类添加一个自定义的方法,这个方法就用来打印学校的名称:
class School {
name: string;
constructor(n: string) {
this.name = n;
}
print() {
console.log(this.name);
}
}
let school1 = new School('学校1');
let school2 = new School('学校2');
let school3 = new School('学校3');
school1.print();
school2.print();
school3.print();
学校有了,现在学校里面还没有老师,我们先把「招聘」老师的招牌挂出去,添加一个teachers属性:
class School {
name: string;
teachers: string[] = [];
constructor(n: string) {
this.name = n;
}
}
然后如果招到一个叫「张三」的老师怎么办呢?我们通过一个方法把这个老师加进来:
class School {
name: string;
teachers: string[] = [];
constructor(n: string) {
this.name = n;
}
addTeacher(name: string) {
this.teachers.push(name);
}
}
let school1 = new School('学校1');
school1.addTeacher('张三');
怎么证明「张三」这个老师已经加进去了呢?再加一个打印老师名单的方法,为了方便观察,我们可以多加几个老师:
class School {
name: string;
teachers: string[] = [];
constructor(n: string) {
this.name = n;
}
addTeacher(name: string) {
this.teachers.push(name);
}
printTeachers() {
console.log(this.teachers);
}
}
let school1 = new School('学校1');
school1.addTeacher('张三');
school1.addTeacher('李四');
school1.addTeacher('王五');
school1.printTeachers();
这样「学校1」-school1
就招到了三名老师,用同样的方法加「学生即可」。
现在有一个问题,就是我现在可以直接通过school1
这个对象来进行修改老师的名字,就好像一个学校外的人盗用了学校的电脑密码,偷偷把电脑里的老师名字改了:
school1[0] = '张二';
这可怎么办……怎么能随随便便让别人修改学校电脑里的数据呢?
private
我们使用private
修饰符来描述teachers
,这样我们就只能在这个类里面来访问和修改这个teachers
。
class School {
private teachers: string[] = [];
}
实际上我们属性前面不加任何东西的时候,它默认是有一个public
修饰符的,也就是公开的。
属性初始化值的简写方式
属性如果没有初始值的话,是需要在新建对象的时候传给构造函数里面进行赋值的:
class School {
name: string;
constructor(name: string) {
this.name = name;
}
}
let school = new School('学校1');
但是如果有很多属性都需要初始化那不是在构造函数里要写很多的this.xxx = xxx
:
class School {
private id: number;
private type: string;
private name: string;
private level: string;
//可能还有很多……
constructor(name: string, id: number, type: string, level: string) {
this.id = id;
this.type = type;
this.name = name;
this.level = level;
//可能还有很多……
}
}
那么有没有一种简写的方式呢?那是必须的,我们把在构造函数的参数前面加上修饰符
就相当于直接新建了这个属性:
class School {
constructor(
private id: number,
private type: string,
private name: string,
private level: string
) { }
}
readonly
类里面有的属性是不需要被修改的,例如学校的id,其实任何数据的id都是不能修改的,因为id是数据的唯一标识,这个标识是永远不会变的,那么我们可以直接给这种属性加一个readonly
修饰符:
class School {
constructor(
private readonly id: number, //只有id是只读的!
private type: string,
private name: string,
private level: string
) { }
}
Inheritance-继承
我们知道「学校」是有不同类型的,例如「幼儿园」、「小学」、「中学」……所以我们可以将School
类再往下细分,假如我们现在再新建一个「小学」类,然后让「小学」继承「学校」:
class School {
private teachers: string[] = [];
private students: string[] = [];
constructor(
private readonly id: number, //只有id是只读的!
private type: string, //学校类型:幼儿园、小学、初中……
private name: string
) { }
}
class ElementarySchool extends School {
constructor(
id: number,
name: string
) {
super(id, '小学', name);
}
}
//通过 ElementarySchool 来新建一所小学
let elementarySchool = new ElementarySchool(1, '一所小学');
继承使用extends
关键字,需要注意的是,如果一要实现继承,我们是需要在子类的构造函数中通过使用super()
方法调用一下父类的构造函数,既然是调用父类的构造函数,那么就需要把初始化的参数传过去。
protected
继续上面继承的盒子,现在我想在子类ElementarySchool
加一个「招学生」-addStudent
的方法:
class School {
private teachers: string[] = [];
private students: string[] = [];
constructor(
private readonly id: number, //只有id是只读的!
private type: string, //学校类型:幼儿园、小学、初中……
private name: string
) { }
}
class ElementarySchool extends School {
constructor(
id: number,
name: string
) {
super(id, '小学', name);
}
addStudent(name: string) { //添加addStudent方法
this.students.push(name);
}
}
这样写发现报错了:
这是因为子类把你类继承过来后,发现父类的students
是private
的,前面说过private
过的属性只能在当前类进行访问修改,其它地方没权限,那么我们可以把这个private
改成protected
,这样我们就可以在子类对它进行操作了:
class School {
//改成 protected
protected teachers: string[] = [];
//改成 protected
protected students: string[] = [];
constructor(
private readonly id: number, //只有id是只读的!
private type: string, //学校类型:幼儿园、小学、初中……
private name: string
) { }
}
class ElementarySchool extends School {
constructor(
id: number,
name: string
) {
super(id, '小学', name);
}
addStudent(name: string) {
this.students.push(name);
}
}
Getters和Setters
下面我们来做一个功能,就是给School
添加一个设置、获取学校信息的方法,根据我们前面学过的方法,可以这样来写:
class School {
name: string = '';
type: string = '';
constructor() { }
setInfo(name: string, type: string): void {
this.name = name;
this.type = type;
}
getInfo(): {
name: string,
type: string
} {
return {
name: this.name,
type: this.type
}
}
}
let school = new School();
school.setInfo('青春活力', '小学');
console.log(school.getInfo());
这样固然可以实现,但我们可以通过Getter和Setter来实现:
type SchoolType = {
name: string,
type: string
}
class School {
name: string = '';
type: string = '';
constructor() { }
//set方法只能接收一个参数!
set info(params: SchoolType) {
this.name = params.name;
this.type = params.type;
}
get info(): SchoolType {
return {
name: this.name,
type: this.type
}
}
}
let school = new School();
//直接给 info 赋值
school.info = {
name: '青春活动',
type: '小学'
}
console.log(school.info);
我们这里使用了set
和get
关键字来定义info
这个属性。注意,info
是一个属性,因为设置和获取属性是一个很常用的操作,所以TS为我们提供了这样一个方式。在set
的时候,它只接收一个参数,因为通过设置属性的方式就可以看出来school.info = ...
,是直接给它赋值来实现的。
static
开一所学校并不是想开就能开的,肯定需要去「教育部」办理一些手续的,那么当我们「新建一所学校」之后,这个学校是不能直接开学的,没办好手续那就是非常开学,现在我们来创建一个「注册学校」的方法,注意,「注册学校」这个操作并不是每个学校都能有的,可能只有「教育部」才有这个权限,所以,我们这个「注册学校」的方法不能让每个new
出来的学校自己可以调用,这样的话就需要创建一个static
方法了:
class School {
constructor(
private readonly id: number,
private name: string,
private type: string
) {
}
static register(school: School) {
console.log(`${school.type}:「${school.name}」注册成功!`)
}
}
let school = new School(1, '青春活力', '小学');
School.register(school);
register
不属于任何一个new
出来的学校,它是属于School
这个类的,这样的方法称之为静态方法,然后静态方法直接通过类名来调用School.register()
:
Abstract Classes-抽象类
我们知道学校类型是固定的,例如「小学」、「中学」等等。那么我们之前例子当中的School
类其实只是一个基类,实际使用的时候,我们可以通过子类继承它来进行新建学校,此时我们就可以将School
改成抽象类,方法很简单,直接在类名前面加上关键字abstract
:
abstract class School {
constructor(
private readonly id: number,
private name: string,
private type: string
) {
}
static register(school: School) {
console.log(`${school.type}:「${school.name}」注册成功!`)
}
}
注意!当类变成了abstract
之后,是不能直接new
的,abstract
这个单词是啥?抽象!!它是抽象的!!我们要使用它,必须继承它,例如,我们让「小学」和「中学」来继承这个类:
abstract class School {
constructor(
private readonly id: number,
private name: string,
private type: string
) {
}
static register(school: School) {
console.log(`${school.type}:「${school.name}」注册成功!`)
}
}
//小学类
class ElementarySchool extends School {
constructor(id: number, name: string, type: string) {
super(id, name, type)
}
}
//中学类
class MiddleSchool extends School {
constructor(id: number, name: string, type: string) {
super(id, name, type)
}
}
这样我们就可以new
学校了,只不过是通过它的子类来进行的。
我们还可以在抽象类里面添加抽象的方法,需要注意的是,抽象方法是必须要在子类里面进行重写的,倒如每个学校都有一个校训口号之类的:
abstract class School {
constructor(
private readonly id: number,
private name: string,
private type: string
) {
}
//抽象方法
abstract printSlogan(): void;
}
class ElementarySchool extends School {
constructor(id: number, name: string, type: string) {
super(id, name, type)
}
//子类必须重写该抽象方法
printSlogan() {
console.log('小学生强则国强!!')
}
}
class MiddleSchool extends School {
constructor(id: number, name: string, type: string) {
super(id, name, type)
}
//子类必须重写该抽象方法
printSlogan() {
console.log('中学生强则国强!!')
}
}
let elementary = new ElementarySchool(1, '小学生的活力', '小学');
let middle = new MiddleSchool(2, '中学生的活力', '中学');
elementary.printSlogan();
middle.printSlogan();
Interfaces-接口
使用接口来描述对象类型
前面我们讲过type
,我们可以使用type来定义一个类型:
type School = {
name: string;
type: string;
printName(): void;
}
let school: School = {
name: '学校1',
type: '小学',
printName() {
console.log(this.name);
}
};
其实我们还可以使用interface
关键字,例如:
interface School {
name: string;
type: string;
printName(): void;
}
let school: School = {
name: '学校1',
type: '小学',
printName() {
console.log(this.name);
}
};
其实在这个例子当中,使用type
还是使用interface
,它们是没什么区别的。
但抛开这个例子,它们还是有一些差别的,那就是interface
大多数时候是用来描述类的:
interface SchoolType {
name: string;
type: string;
printName(): void;
}
class School implements SchoolType {
constructor(
public name: string,
public type: string
) {
}
printName() {
console.log(this.name);
}
}
let school = new School('学校1', '小学');
school.printName();
从上面的代码中可以看到类是通过implements
关键字来让接口描述它的,这个动作,我们称之为「实现」接口,即School
实现SchoolType
这个接口。
接口的继承
前面我们说过类是可以通过extends
来实现继承的,其实接口之间也是可以实现继承的:
interface Named {
name: string;
}
interface SchoolType extends Named {
type: string;
printName(): void;
}
class School implements SchoolType {
}
如果说有的属性和方法不是必须实现的,那么直接在属性或方法名后面加上一个问号?
即可:
interface Named {
//添加 ?
name?: string;
}
interface SchoolType extends Named {
type: string;
//添加 ?
printName?(): void;
}
class School implements SchoolType {
}
使用接口来描述函数
之前我们使用type
来定义过函数类型:
type addFn = (num1: number, num2: number) => number;
换成interface
来定义,只是写法不一样,实现效果是一样的:
interface addFn {
(num1: number, num2: number): number;
}
!!!毫无征兆!突然结束!下次再聊!!!