🪀 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目前为止:属性
waterAmount
和power
是公共的。可以轻松地从外部将它们获取或者设置成任何值。将
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
方法。
提示
getter
和 setter
函数的用法:
class CoffeeMachine {
_waterAmount = 0;
setWaterAmount(value) {
if (value < 0) value = 0;
this._waterAmount = value;
}
getWaterAmount() {
return this._waterAmount;
}
}
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
使外部能访问私有属性,利用
getter
和setter
访问器方法: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
}
}
2
3
4
5
注意
私有字段不能通过 this[name]
方式访问。
通常普通的字段可以通过 this[name]
访问:
class User {
...
sayHi() {
let fieldName = "name";
console.log(`Hello, ${this[fieldName]}`);
}
}
2
3
4
5
6
7
假如有私有字段
#name
,this['#name']
不起作用。
# 总结
隐藏内部接口,保护了内部的属性和方法,有效限制了来自外部的操作。可以隐藏内部的复杂实现细节,只提供给外部 方便简洁的访问方式。
隐藏内部接口使用 受保护的字段 或者 私有字段:
- 受保护的字段以
_
开头。这是一个约定,不是在语言级别强制执行的。程序员应该只通过它的类和从它继承的类中访问以_
开头的字段。 - 私有字段以
#
开头。是 JavaScript 语言级别的强制执行的。
- 受保护的字段以