愿你坚持不懈,努力进步,进阶成自己理想的人

—— 2017.09, 写给3年后的自己

Typescript(叁):类

一、基本用法

可以使用class关键字来声明一个类,类里面可以声明属性方法,如:

class ClassName {
    prop: type // 声明属性
    // 声明构造器
    constructor() {
    }
    // 声明方法
    methodName() {
    }
}

然后可以使用new关键字来实例化一个类,如:let p = new ClassName()


二、继承

typescript里,同样可以使用常用的面向对象模式,如类的继承,继承使用extends关键字,需要注意的是,一旦子类里显式声明了constructor方法,那么方法内部就必须使用super()方法来调用父类构造器,示例如:

class A {
    prop1: string
    method1() {
        // ...
    }
}

class B extends A {
    prop2: number
    constructor() {
        super()
    }
    method2() {
        // ...
    }
}


三、访问控制

1、public/private/protected

typescript支持与其他语言类似的访问控制修饰符:publicprivateprotected,可以在属性方法的前面加上访问控制符来控制访问权限,默认情况下,访问权限为public,示例如:

class A {
    prop1: string           // 不加修饰符,默认为public,可被类自身、类外、子类访问
    public prop2: string    // 可被类自身、类外、子类访问
    private prop3: string   // 只能被类自身访问,类外、子类不能访问
    protected prop4: string // 只能被类自身、子类访问,类外不能访问
}

注意:typescript使用的是结构性类型系统,所以当比较两种不同的类型时,如果所有的成员的类型都是兼容的,那么这两个类型就是兼容的,如:

class A {
    prop1: string
}
class B {
    prop1: string
    prop2: string
}
let instance:A = new B() // 允许这么做,因为A的所有成员类型,B中都有

但是如果被比较的类里面含有privateprotected类型成员的时候,情况就不同了,这时候需要另一个类里也含有这个private/protected成员,类型才能是兼容的,所以有:

class A {
    private prop1: string
}
class B {
    private prop2: string
}

let p1:A = new B() // 报错

class C extends A {
}

let p2:A = new C() // 允许这么做

2、readonly修饰符

可以使用readonly将属性设为只读的,只读属性必须在声明时或者构造函数里初始化,如:

class A {
    readonly prop1: string
}
let a = new A()
a.prop1 = 'HelloWorld' // 报错,因为只能在声明时或者构造函数里初始化

所以正确的方式为:

class A {
    readonly prop1: string = 'HelloWorld'
}
// 或者
class A {
    readonly prop1: string
    constructor() {
        this.prop1 = 'HelloWorld'
    }
}

3、参数属性

参数属性允许同时创建初始化成员,可以把声明和赋值合并至一处,如:

class A {
    constructor(private name: string) {
    }
}

这等价于:

class A {
    private name: string
    constructor(name: string) {
        this.name = name
    }
}

4、存取器

typescript支持gettersetter,但是有一点限制:编译器输出必须设为ES5或者更高,不支持降级到ES3。getter和setter的用法如:

class A {
    get prop1() {
        // ...
    }
    set prop1() {
        // ...
    }
}
let a:A = new A()
a.prop1 // 此时调用getter
a.prop1 = xxx // 此时调用setter

当一个存取器只带有get却不带有set时,它会被自动推断为readonly

5、静态属性

可以使用static来定义类里的静态属性,静态属性属于类自身,而不属于实例,访问的时候要用类名访问,而不能用实例对象访问,如:

class ClassName {
    static propName: string
}
ClassName.propName // 可以访问

let p = new ClassName()
p.propName // 不能访问


四、抽象类

抽象类只能作为其他派生类的基类使用,抽象类不能被实例化,它具有如下特点:
1)抽象类可以包含成员的实现细节,且抽象类必须用abstract声明
2)抽象类里不含方法体的方法称为抽象方法,使用abstract声明,抽象方法必须被子类实现(抽象方法必须使用abstract关键字声明,且可以包含访问修饰符)
所以有:

abstract class AbstractClass {
    greet() {
        console.log('Hello, world')
    }
    abstract method()
}
let p = new AbstractClass() // 不允许

// 以下代码会报错,因为没有实现全部的抽象方法
class A extends AbstractClass {
}

3)private修饰符不能与abstract修饰符一起使用
4)使用abstract修饰符,则子类中需要有同样或更高的访问权限,如:

// 情况一:报错
abstract class AbstractClass {
    private abstract a
}
class Sub extends AbstractClass {
    private a
}
// 情况二:可行
abstract class AbstractClass {
    protected abstract a
}
class Sub extends AbstractClass {
    public a
}
// 情况三:不可行
abstract class AbstractClass {
    protected abstract a
}
class Sub extends AbstractClass {
    private a
}