扩展@loganfsmyth 的答案:
JavaScript 中唯一真正私有的数据仍然是作用域变量。在内部访问属性的方式与公共属性相同,您不能拥有私有属性,但您可以使用作用域变量来存储私有数据。
作用域变量
这里的做法是使用构造函数的作用域,也就是私有的,来存储私有数据。对于能够访问这些私有数据的方法,它们也必须在构造函数中创建,这意味着您正在使用每个实例重新创建它们。这是性能和内存损失,但有些人认为这种损失是可以接受的。通过像往常一样将它们添加到原型中,可以避免不需要访问私有数据的方法的惩罚。
例子:
function Person(name) {
let age = 20; // this is private
this.name = name; // this is public
this.greet = function () {
// here we can access both name and age
console.log(`name: ${this.name}, age: ${age}`);
};
}
let joe = new Person('Joe');
joe.greet();
// here we can access name but not age
作用域弱映射
可以使用 WeakMap 来避免先前方法的性能和内存损失。 WeakMaps 将数据与对象(此处为实例)相关联,使得数据只能使用该 WeakMap 进行访问。因此,我们使用作用域变量方法创建一个私有 WeakMap,然后使用该 WeakMap 检索与this 关联的私有数据。这比作用域变量方法更快,因为您的所有实例都可以共享一个 WeakMap,因此您无需重新创建方法来让它们访问自己的 WeakMap。
例子:
let Person = (function () {
let privateProps = new WeakMap();
class Person {
constructor(name) {
this.name = name; // this is public
privateProps.set(this, {age: 20}); // this is private
}
greet() {
// Here we can access both name and age
console.log(`name: ${this.name}, age: ${privateProps.get(this).age}`);
}
}
return Person;
})();
let joe = new Person('Joe');
joe.greet();
// here we can access joe's name but not age
这个例子使用一个Object来为多个私有属性使用一个WeakMap;您还可以使用多个 WeakMap 并像 age.set(this, 20) 一样使用它们,或者编写一个小包装器并以其他方式使用它,例如 privateProps.set(this, 'age', 0)。
理论上可以通过篡改全局WeakMap 对象来破坏这种方法的隐私。也就是说,所有 JavaScript 都可以被损坏的全局变量破坏。我们的代码已经建立在假设不会发生这种情况的基础上。
(此方法也可以使用Map 完成,但WeakMap 更好,因为Map 会造成内存泄漏,除非您非常小心,为此两者并没有其他不同。)
半答案:作用域符号
符号是一种可以用作属性名称的原始值。您可以使用作用域变量方法创建一个私有符号,然后将私有数据存储在this[mySymbol]。
使用Object.getOwnPropertySymbols 可以破坏这种方法的隐私,但这样做有点尴尬。
例子:
let Person = (function () {
let ageKey = Symbol();
class Person {
constructor(name) {
this.name = name; // this is public
this[ageKey] = 20; // this is intended to be private
}
greet() {
// Here we can access both name and age
console.log(`name: ${this.name}, age: ${this[ageKey]}`);
}
}
return Person;
})();
let joe = new Person('Joe');
joe.greet();
// Here we can access joe's name and, with a little effort, age. ageKey is
// not in scope, but we can obtain it by listing all Symbol properties on
// joe with `Object.getOwnPropertySymbols(joe)`.
半答案:下划线
旧的默认值,只使用带有下划线前缀的公共属性。尽管在任何方面都不是私有财产,但这种约定非常普遍,它很好地传达了读者应该将财产视为私有的,这通常可以完成工作。作为这种失误的交换,我们得到了一种更容易阅读、更容易输入和更快的方法。
例子:
class Person {
constructor(name) {
this.name = name; // this is public
this._age = 20; // this is intended to be private
}
greet() {
// Here we can access both name and age
console.log(`name: ${this.name}, age: ${this._age}`);
}
}
let joe = new Person('Joe');
joe.greet();
// Here we can access both joe's name and age. But we know we aren't
// supposed to access his age, which just might stop us.
结论
截至 ES2017,仍然没有完美的方法来处理私有属性。各种方法各有利弊。作用域变量是真正私有的; scoped WeakMaps 是非常私有的,比 scoped 变量更实用; scoped Symbols 具有合理的私密性和实用性;下划线通常足够私密且非常实用。