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);
    }
}

这样写发现报错了:

这是因为子类把你类继承过来后,发现父类的studentsprivate的,前面说过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);

我们这里使用了setget关键字来定义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;
}

!!!毫无征兆!突然结束!下次再聊!!!