ECMAScript 类 - 保持私有性
最初发布于devinduct
介绍
和往常一样,我们先从一些理论解释开始。ES 类是 JavaScript 中新的语法糖。它们提供了一种简洁的方式来编写和实现与使用原型链相同的功能。唯一的区别在于它看起来更美观,而且如果你之前接触过 C# 或 Java,你会觉得它更自然。有人可能会说它们并非为 JavaScript 而设计,但就我个人而言,我并不介意使用类或 ES5 原型标准。
它们提供了一种更简便的封装方式,可以创建一组固定的方法,用于操作具有有效内部状态的实体。简而言之,我们可以用更少的代码实现更多的功能,这正是关键所在。借助它们,JavaScript 正朝着面向对象的方式发展,通过使用它们,我们将应用程序拆分成对象而不是函数。别误会,将应用程序拆分成函数并非坏事,实际上,这是一件好事,它比类有一些优势,但这又是另一个话题了。
更实际地说,每当我们想在应用程序中描述现实世界的模型时,我们都会使用类来实现。例如,建筑物、汽车、摩托车等等。它们代表现实世界中的实体。
范围
在服务器端语言中,我们有访问修饰符或可见性级别,例如public` privatepublic`、protected`public`、` internalpublic`、`public`package等等。遗憾的是,JavaScript 只支持前两种访问修饰符,而且支持的方式也各不相同。我们不会使用 `public`public或 ` privatepublic` 来声明字段,JavaScript 在某种程度上假定所有作用域都是 `public` 的,这也是我写这篇文章的原因。
请注意,我们可以在类中声明私有字段和公共字段,但这些字段声明是一项实验性功能,因此目前还不能安全使用。
class SimCard {
number; // public field
type; // public field
#pinCode; // private field
}
如果没有像 Babel 这样的编译器,则不支持像上面这样的字段声明。
保护隐私——封装
在编程中,封装是指将某些内容保护起来,使其对外部世界隐藏。通过将数据设为私有,使其仅对所有者可见,我们就实现了数据封装。本文将介绍几种数据封装方法。让我们深入了解一下。
1. 根据惯例
这实际上只是伪造private数据或变量的状态。实际上,它们是公开的,任何人都可以访问。我遇到的两种最常见的私有化约定是 `-p`$和 ` _-v` 前缀。如果某个属性以这些符号之一作为前缀(通常整个应用程序只会使用一个),则应将其视为该特定对象的非公共属性。
class SimCard {
constructor(number, type, pinCode) {
this.number = number;
this.type = type;
// this property is intended to be a private one
this._pinCode = pinCode;
}
}
const card = new SimCard("444-555-666", "Micro SIM", 1515);
// here we would have access to the private _pinCode property which is not the desired behavior
console.log(card._pinCode); // outputs 1515
2. 隐私与封闭
闭包在维护变量作用域方面非常有用。它们由来已久,几十年来一直被 JavaScript 开发者使用。这种方法能真正保护隐私,数据不会被外部访问,只能由所有者实体管理。我们将在类构造函数中创建局部变量,并使用闭包将其捕获。为了使其生效,这些方法必须附加到实例上,而不是定义在原型链上。
class SimCard {
constructor(number, type, pinCode) {
this.number = number;
this.type = type;
let _pinCode = pinCode;
// this property is intended to be a private one
this.getPinCode = () => {
return _pinCode;
};
}
}
const card = new SimCard("444-555-666", "Nano SIM", 1515);
console.log(card._pinCode); // outputs undefined
console.log(card.getPinCode()); // outputs 1515
3. 使用符号和 Getter 实现隐私
Symbol 是 JavaScript 中的一种新的原始数据类型。它在 ECMAScript 版本 6 中引入。每次调用返回的值Symbol()都是唯一的,这种类型的主要用途是用作对象属性标识符。
由于我们的目标是在类定义之外创建符号,但又不想将其设为全局符号,因此引入了模块。这样,我们就可以在模块级别创建私有字段,在构造函数中将其附加到类对象,并从类的 getter 方法中返回符号键。需要注意的是,我们也可以使用原型链上创建的标准方法,而不是使用 getter 方法。我之所以选择使用 getter 方法,是因为我们不需要调用函数来获取值。
const SimCard = (() => {
const _pinCode = Symbol('PinCode');
class SimCard {
constructor(number, type, pinCode) {
this.number = number;
this.type = type;
this[_pinCode] = pinCode;
}
get pinCode() {
return this[_pinCode];
}
}
return SimCard;
})();
const card = new SimCard("444-555-666", "Nano SIM", 1515);
console.log(card._pinCode); // outputs undefined
console.log(card.pinCode); // outputs 1515
这里需要指出的一点是这个Object.getOwnPropertySymbols方法。这个方法可以用来访问我们原本打算设为私有的字段。_pinCode我们可以这样从类中检索值:
const card = new SimCard("444-555-666", "Nano SIM", 1515);
console.log(card[Object.getOwnPropertySymbols(card)[0]]); // outputs 1515
4. 使用 WeakMap 和 Getters 保护隐私
Map它们WeakMap也在 ECMAScript 版本 6 中引入。它们以键值对格式存储数据,因此非常适合存储私有变量。在我们的示例中,WeakMap每个属性都在模块级别定义了一个私有属性,并在类构造函数中设置了每个私钥。值由类的 getter 方法获取,之所以选择这种方法,是因为我们不需要调用函数来获取值。
const SimCard = (() => {
const _pinCode = new WeakMap();
const _pukCode = new WeakMap();
class SimCard {
constructor(number, type, pinCode, pukCode) {
this.number = number;
this.type = type;
_pinCode.set(this, pinCode);
_pukCode.set(this, pukCode);
}
get pinCode() {
return _pinCode.get(this);
}
get pukCode() {
return _pukCode.get(this);
}
}
return SimCard;
})();
const card = new SimCard("444-555-666", "Nano SIM", 1515, 45874589);
console.log(card.pinCode); // outputs 1515
console.log(card.pukCode); // outputs 45874589
console.log(card._pinCode); // outputs undefined
console.log(card._pukCode); // outputs undefined
结论与延伸阅读
希望这些例子对您有所帮助,并能融入到您的工作流程中。如果您觉得有用,并且喜欢这篇文章,请分享出去。
如需进一步阅读,我推荐这篇关于JavaScript 代码整洁最佳实践的文章。
感谢阅读,我们下篇文章再见。
文章来源:https://dev.to/proticm/ecmascript-classes-keeping-things-private-1d34