TypeScript真香系列-类

1,286 阅读5分钟

前言

TypeScript真香系列的内容将参考中文文档,但是文中的例子基本不会和文档中的例子重复,对于一些地方也会深入研究。另外,文中一些例子的结果都是在代码没有错误后编译为JavaScript得到的。如果想实际看看TypeScript编译为JavaScript的代码,可以访问TypeScript的在线编译地址,动手操作,印象更加深刻。

类的基础

TypeScript中的类和ES6中的类十分相似,从下面这个例子就能够看出来了:

class Man{
    name: string;
    constructor(msg: string) {
        this.name= msg;
    }
    play() {
        return "Hello, " + this.name;
    }
}

let man = new Man("James");
man.name; //"James"
man.play(); //"Hello, James"

我们在上面声明了一个Man类,类中有三个成员:分别为name属性,构造函数,play方法。

继承

TypeScript中类的继承也和ES6中类似,派生类(子类)可以从基类(父类)中继承属性和方法。

class Man{
    name: string;
    constructor(msg: string) {
        this.name= msg;
    }
    play() {
        return "Hello, " + this.name;
    }
}
class Player extends Man {
    age: string;
    constructor(name: string,age:string) {
        super(name)
        this.age = age;
    }
    showAge() {
        return this.age;
    }
} 

let player = new Player("James","35");
player.age; //"35"
player.name; //"James"
player.play(); //"Hello, James"

在上面的代码中,Man类就是我们的基类,Player 类就是我们的派生类。通过关键字extends ,我们就可以实现类的继承。若派生类中包含了构造函数,则必须调用super(),它会执行基类的构造函数。

公共,私有与受保护的修饰符

public

在TypeScript中,成员都是默认标记为public,可以理解为公开,所以我们可以自由的访问程序里面定义的成员。当然我们也可以显式的标记出来:

class Man{
    public name: string;
    public constructor(msg: string) {
        this.name= msg;
    }
    public play() {
        return "Hello, " + this.name;
    }
}

private

我们可以把类中的成员标记为private,可以理解为私有的。一旦成员标记标记为private,我们就不能在它的类的外部访问它。

class Man {
    private name: string;
    constructor(msg: string) {
        this.name = msg;
    }
}

new Man("James").name; // 错误: 'name' 是私有的.

并且在Man的派生类中也是不能访问的:

class Man {
    private name: string;
    constructor(msg: string) {
        this.name = msg;
    }
}

class Player extends Man {
    constructor(name: string) {
        super(name);
    }
}

let player = new Player("James");
player.name; //错误, 'name' 是私有的.

如果想要访问Man类中的name,那么我们可以在Man类中定义一个方法show():

class Man {
    private name: string;
    constructor(msg: string) {
        this.name = msg;
    }
    show() {
        return this.name;
    }
}

class Player extends Man {
    constructor(name: string) {
        super(name);
    }
}

let player = new Player("James");
plager.show(); //"James"

我们还可以用下面这个修饰符来进行类似的操作。

protected

protected,可理解为受保护的,和private相似,有一点不同的就是,protected成员可以在派生类中可以访问:

class Man {
    protected name: string;
    constructor(msg: string) {
        this.name = msg;
    }
}

class Player extends Man {
    constructor(name: string) {
        super(name);
        this.name = name;
    }
    show() {
        return this.name
    }
    
}

let man = new Man("James"); 
man.name; //错误, 'name' 是受保护的.
let player = new Player("James");
player.name; //错误, 'name' 是受保护的.
player.show(); // "James"

protected当然也可以标记构造函数。当构造函数被protected标记后,当前类就不能被实例化了,但是可以被派生类继承:

class Man {
    protected name: string;
    protected constructor(msg: string) {
        this.name = msg;
    }
}

class Player extends Man {
    constructor(name: string) {
        super(name);
        this.name = name;
    }
}

let man = new Man("James");  //错误,Man的构造函数是受保护的
let player = new Player("James");

readonly修饰符

我们可以使用readonly将成员设置为只读,只读属性必须在声明时或构造函数里被初始化。

class Man {
    readonly name: string;
    constructor(msg: string) {
        this.name = msg;
    }
}

let man = new Man("James");
man.name = "wade"; //错误,name是只读属性

我们还有另一种写法:

class Man {
    constructor(readonly name: string) {
        
    }
}

let man = new Man("James");

对比上面种写法,发现下面这种方式简洁了许多,这是我们使用了参数属性。参数属性可以让我们方便的在一个地方定义并初始化一个成员,而且可以通过给构造函数前面添加一个访问限定符(public,private,protected)来进行声明。如下所示:

//使用前
class Man {
    public name:string
    constructor(name: string) {
        this.name = name
    }
}
//使用后
class Man {
    constructor(public name: string) {
        
    }
}

存取器

在TypeScript中,我们可以利用存取器来帮助我们有效的控制对对象成员的访问。 首先看没有存取器的版本:

class Man {
    public name: string;
    constructor(msg: string) {
        this.name = msg
    }
}

let man = new Man("James");
man.name = "wade";
console.log(man.name); // wade

然后是存取器版本:

class Man {
    public _name: string;
    constructor(msg: string) {
        this._name = msg;
    }
    get myName(): string{
        return this._name;
    }
    set myName(newName: string) {
        this._name = newName;
    }
}

let man = new Man("James");
console.log(man.myName); // James
man.myName = "wade";
console.log(man.myName); // wade

这里有两个地方需要注意:一是,存取器要求我们配置时将编译器设置为输出ECMAScript 5或更高的版本;二是,存取器只带get不带set的话,存取器会自动推断为readonly。

静态属性

TypeScript的静态属性和JavaScript中的静态属性是类似的。我们创建的类的静态成员,这些属性是在类的本身而不是在类的实例上:

class Man {
    static player: string = "运动员";
    show() {
        return "我职业:" + Man.player
    }
}
let man = new Man();
man.player; //错误,player是静态成员
Man.player; // 运动员
man.show(); // 我职业:运动员

抽象类

TypeScript中的抽象类是提供其他派生类的基类,但是不能直接被实例化。我们可以用abstract关键字来定义抽象类和抽象类中的方法。其用法如下:

abstract class Man {

    constructor(public name: string) {
        
    }
    show() {
        return "我的名字:" + this.name
    }

    abstract play(type:string): void; //这个方法必须在派生类中实现,不然派生类就会报错
}

class Player extends Man {

    constructor(name:string) {
        super(name)   //派生类中的构造函数必须调用super()
    }
    play(type:string) {
        return "我的职业是" + type;
    }
    go() {
        return "冲冲冲";
    }
}
let man: Man;  //可以创建一个对抽象类的引用
man = new Man("James"); // 错误,不能创建一个抽象类的实例
man = new Player("Wade"); //可以对一个抽象子类进行进行实例化和赋值
man.go();    //错误,go方法在声明的抽象类中不存在
man.play("basketball"); //"我的职业是basketballer"

参考

github.com/zhongsp/Typ…

最后

文中有些地方可能会加入一些自己的理解,若有不准确或错误的地方,欢迎指出~ 祝大家新年快乐,阖家辛福! 希望前线的医护人员健健康康,早日打败病毒! 希望大家都健健康康!