目录

🪀 JavaScript 类的私有和受保护的属性和方法

一般在 面向对象编程 中,属性应该分为两组:内部接口和外部接口:

  • 内部接口 :可以通过该类的其他方法访问,但不能从外部访问的方法和属性。内部接口用于对象工作,一般处理细节。
  • 外部接口 :可以从类的外部访问的类的方法和属性。使用一个对象时只需知道它的外部接口,可能不必知道内部如何工作的细节。

所以在 JavaScript 的类中,有两种类型的对象字段(属性和方法):

  • 公共的属性和方法:可从任何地方访问。它们构成了外部接口。

  • 私有的属性和方法:只能从类的内部访问。这些用于内部接口。

  • 受保护的属性和方法:只能从类的内部和基于其扩展的类的内部访问。它们对于内部接口也很有用。从某种意义上讲,它们比私有的属性和方法更为广泛,通常希望继承类来访问它们。

    受保护的字段不是在语言级别的 Javascript 中实现的。它们是在 Javascript 中模拟的类定义语法。

🌰 咖啡机例子:

  • 咖啡机类:

    class CoffeeMachine {
      waterAmount = 0;
    
      constructor(power) {
        this.power = power;
        console.log( `Created a coffee-machine, power: ${power}` );
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
  • 创建实例、使用:

    // 创建咖啡机
    let coffeeMachine = new CoffeeMachine(100);
    
    // 加水
    coffeeMachine.waterAmount = 200;
    
    1
    2
    3
    4
    5

    目前为止:属性 waterAmountpower 是公共的。可以轻松地从外部将它们获取或者设置成任何值。

  • waterAmount 修改为受保护的属性:

    受保护的属性通常以下划线 _ 作为前缀。

    class CoffeeMachine {
      _waterAmount = 0;
    
      set waterAmount(value) {
        if (value < 0) {
          value = 0;
        }
        this._waterAmount = value;
      }
    
      get waterAmount() {
        return this._waterAmount;
      }
    
      constructor(power) {
        this._power = power;
      }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
  • 创建实例、使用:

    // 创建咖啡机
    let coffeeMachine = new CoffeeMachine(100);
    
    // 加水
    coffeeMachine.waterAmount = -10; // _waterAmount 将变为 0,而不是 -10
    
    1
    2
    3
    4
    5

    此时因为 waterAmount 是「受保护的属性」,设值或成经过 set 方法控制。

  • power 为只读属性:

    class CoffeeMachine {
      // ...
    
      constructor(power) {
        this._power = power;
      }
    
      get power() {
        return this._power;
      }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    此时,试图修改 power 的操作都会报错,因为没有对应的 set 方法。

提示

gettersetter 函数的用法:

class CoffeeMachine {
  _waterAmount = 0;

  setWaterAmount(value) {
    if (value < 0) value = 0;
    this._waterAmount = value;
  }

  getWaterAmount() {
    return this._waterAmount;
  }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14

函数更灵活。它们可以接受多个参数。

但是 set / get 的简化方法也可用。

提示

因为 「受保护的属性」只是一个利用代码逻辑实现的字段,所以它是可以 被继承的

# 私有属性

新添加的 JavaScript 特性:使用 # 开头定义 私有的属性和方法。它们只能在类的内部可以被访问

  • 在语言级别, # 是该字段为私有的特殊标志。
  • 私有字段与公共字段不会发生冲突。可以同时拥有两个同名的 公共字段和私有字段。

🌰 例子:

class CoffeeMachine {
  #waterLimit = 200;

  #fixWaterAmount(value) {
    if (value < 0) return 0;
    if (value > this.#waterLimit) return this.#waterLimit;
  }

  setWaterAmount(value) {
    this.#waterLimit = this.#fixWaterAmount(value);
  }
}

let coffeeMachine = new CoffeeMachine();

// 不能从类的外部访问类的私有属性和方法
coffeeMachine.#fixWaterAmount(123); // Error
coffeeMachine.#waterLimit = 1000; // Error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • 使外部能访问私有属性,利用 gettersetter 访问器方法:

    class CoffeeMachine {
    
      #waterAmount = 0;
    
      get waterAmount() {
        return this.#waterAmount;
      }
    
      set waterAmount(value) {
        if (value < 0) value = 0;
        this.#waterAmount = value;
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

提示

与受保护的字段不同,私有字段是语言级别的。所以私有字段并不能直接继承,只能通过访问器访问。

🌰 例子:

class MegaCoffeeMachine extends CoffeeMachine {
  method() {
    console.log(this.#waterAmount); // Error: can only access from CoffeeMachine
  }
}
1
2
3
4
5

注意

私有字段不能通过 this[name] 方式访问。

通常普通的字段可以通过 this[name] 访问:

class User {
  ...
  sayHi() {
    let fieldName = "name";
    console.log(`Hello, ${this[fieldName]}`);
  }
}
1
2
3
4
5
6
7

假如有私有字段 #namethis['#name'] 不起作用。

# 总结

  • 隐藏内部接口,保护了内部的属性和方法,有效限制了来自外部的操作。可以隐藏内部的复杂实现细节,只提供给外部 方便简洁的访问方式

  • 隐藏内部接口使用 受保护的字段 或者 私有字段:

    • 受保护的字段以 _ 开头。这是一个约定,不是在语言级别强制执行的。程序员应该只通过它的类和从它继承的类中访问以 _ 开头的字段。
    • 私有字段以 # 开头。是 JavaScript 语言级别的强制执行的。
📢 上次更新: 2022/09/02, 10:18:16