成员的可访问性

TypeScript 中,类的成员拥有 3 个级别的可访问性——publicprotectedprivate。这 3 个级别的关键字可以以前缀的形式添加到类的各成员上,它们将决定一个成员是否可以在类以外的代码中调用,以及是否可以由子类的代码调用。本节将分别介绍各个级别的可访问性。

public

public 是所有成员的默认可访问性级别,通常不需要增加此前缀。

当成员的可访问性为 public 时,则意味着该成员既可以在类以外的代码中调用,也可以由该类或其子类的代码调用,示例代码如下。由于 public 是默认级别,因此以下代码中的 public 前缀都可以省略。

class Person {
    public age: number = 12;
    public get name() { return "Kiddy"; }
    public location() { return "SiChuan" }

}

//在类以外的代码中访问各个Public级别的成员
let person1: Person = new Person();
person1.age = 17;
console.log(person1.name);
console.log(person1.location());

//在子类中访问各个Public级别的成员
class Talker extends Person {
    public selfIntroduction() {
        console.log(`My name is ${this.name}, I'm ${this.age} years old. I live in
        ${this.location()}`)
    }
}

protected

protected 级别的成员为受保护的成员,它们不能在类以外的代码中访问,只能在它所在类或者所在类的子类中访问。

例如,以下代码将 Person 类的 3 个成员改为 protected 级别,在类以外的代码中访问它们将引起编译错误。

class Person {
    protected age: number = 12;
    protected get name() { return "Kiddy"; }
    protected location() { return "SiChuan" }
}

//在类以外的代码中访问各个protected级别的成员
let person1: Person = new Person();
//编译错误:属性"age"受保护,只能在类"Person"及其子类中访问。ts(2445)
person1.age = 17;
//编译错误:属性"name"受保护,只能在类"Person"及其子类中访问。ts(2445)
console.log(person1.name);
//编译错误:属性"location"受保护,只能在类"Person"及其子类中访问。ts(2445)
console.log(person1.location());

虽然不能在外部访问 protected 级别的成员,但在子类中访问各个 protected 级别的成员是允许的。示例代码如下。

class Talker extends Person {
    public selfIntroduction() {
        console.log(`My name is ${this.name}, I'm ${this.age} years old. I live in
        ${this.location()}`)
    }
}

let talker1: Talker = new Talker();
talker1.selfIntroduction(); //输出"My name is Kiddy, I'm 12 years old. I live in SiChuan"

private

private 级别的成员为私有成员,只可以在其所在类中访问,在类以外的代码和子类中均无法访问。

例如,以下代码将 Person 类的 3 个成员都改为 private 级别的成员,因此无论是在类以外的代码中访问各个 private 级别的成员,还是在子类中访问各个 private 级别的成员,都会引起编译错误。

class Person {
    private age: number = 12;
    private get name() { return "Kiddy"; }
    private location() { return "SiChuan" }
}

//在类以外的代码中访问各个private级别的成员
let person1: Person = new Person();
//编译错误:属性"age"为私有属性,只能在类"Person"中访问。ts(2341)
person1.age = 17;
//编译错误:属性"name"为私有属性,只能在类"Person"中访问。ts(2341)
console.log(person1.name);
//编译错误:属性"location"为私有属性,只能在类"Person"中访问。ts(2341)
console.log(person1.location());

//在子类中访问各个Public级别的成员
class Talker extends Person {
    public selfIntroduction() {
        //编译错误:属性"age"为私有属性,只能在类"Person"中访问。ts(2341)
        //编译错误:属性"name"为私有属性,只能在类"Person"中访问。ts(2341)
        //编译错误:属性"location"为私有属性,只能在类"Person"中访问。ts(2341)
        console.log(`My name is ${this.name}, I'm ${this.age} years old. I live in
        ${this.location()}`)
    }
}

由于 private 级别的成员只能在其所在类对应的代码中使用,因此如果将 selfIntroduction() 方法搬到 Person 类中,就可以正常访问这些 private 级别的成员。例如,以下代码可以正常编译、执行。

class Person {
    private age: number = 12;
    private get name() { return "Kiddy"; }
    private location() { return "SiChuan" }

    public selfIntroduction() {
        console.log(`My name is ${this.name}, I'm ${this.age} years old. I live in
        ${this.location()}`)
    }
}

前面介绍了存取器的概念。在使用存取器时,通常会将封装的属性设置为 private,以避免该属性被外部代码直接调用。只允许外部代码经过存取器来访问该属性。示例代码如下。

class Person {
    private _name: string;
    public get name() {
        return this._name;
    }
    public set name(value: string) {
        this._name = value;
    }
}

虽然 private 级别的成员无法在类以外的代码中访问,但通过一些方法可以绕过这一层检查,例如,以下代码通过索引的形式访问对象的属性,这种方式将绕过编译检查,直接访问 private 属性。在实际项目中应避免使用这类用法。

class A {
    private b: number = 12;
}

let a: A = new A();
console.log(p1["b"]); //输出12

可访问性的兼容性

在定义存取器时,如果同一个存取器的 get 部分与 set 部分的可访问性不一致,将引起编译错误。示例代码如下。

class User {
    private _name: string;

    //编译错误:get访问器必须具有与get访问器相同的可访问性。ts(2808)
    public set name(value: string) { this._name = value; }
    protected get name() { return this._name; }
}

当子类在重写父类的成员时,如果同名可访问性不同,也会引起编译错误。示例代码如下。

class A {
    private a: string;
    protected b: number;
    public c: boolean;
}

//编译错误:类"B"错误扩展基类"A"。ts(2415)
class B extends A {
    public a: string;
    private b: number;
    protected c: boolean;
}

但是,在重写父类成员时子类的可访问性不同,TypeScript 允许一个例外:将基类的 protected 级别的成员提升为 public 级别的成员。例如,以下代码能够正常编译、执行。

class User {
    protected name: string;
    protected sayHello() { }
}
class Guest extends User {
    public name: string;
    public sayHello() { }
}