是否可以在ES6类中创建私有属性?
举个例子。 如何阻止访问instance.property?
class Something {
constructor(){
this.property = "test";
}
}
var instance = new Something();
console.log(instance.property); //=> "test"
是否可以在ES6类中创建私有属性?
举个例子。 如何阻止访问instance.property?
class Something {
constructor(){
this.property = "test";
}
}
var instance = new Something();
console.log(instance.property); //=> "test"
当前回答
除了给出的答案之外,您还可以使用代理来创建“私有属性”,使代理只对公共代码可用。实例只对构造函数、绑定方法和作为接收者的Proxy本身可用。
这比使用符号和弱映射有一些优势。
符号是可枚举的,可以用代理捕获。 当实例被代理为实例时,WeakMaps失败!== new Proxy(instance)
WeakMap失败。
const map = new WeakMap()
const instance = new SomeClass()
map.set(instance, 'foo')
// somewhere along the way in 3rd party code
const proxy = new Proxy(instance, {})
assert(map.set(instance) === map.get(proxy)) // fail
const proxy2 = new Proxy(proxy, {})
// more headache
使用代理用私有道具验证来装饰实例
getProxy = (instance) => new Proxy(instance, {
get: (target, name, receiver) => {
console.log('get', { target, name, receiver })
if (name[0] === '_') throw new Error('Cannot access private property ' + name)
return Reflect.get(target, name, receiver)
},
set: (target, name, value, receiver) => {
console.log('set', { target, name, value, receiver })
if (name[0] === '_') throw new Error('Cannot set private property ' + name)
return Reflect.set(target, name, value, receiver)
}
})
class PublicClass {
constructor() {
Object.defineProperty(this, '_privateProp', { enumerable: false, writable: true, configurable: false })
return getProxy(this) // can be moved out as a decorator
}
getPrivatePropFail() {
return this._privateProp // fail
}
getPrivateProp = () => {
return this._privateProp // ok
}
setPrivateProp = (value) => {
return this._privateProp = value // ok
}
}
pub = new PublicClass()
try {
console.log('get pub._privateProp', pub._privateProp)
} catch(e) {
console.error(e)
}
try {
console.log('set pub._privateProp', pub._privateProp = 'you fail')
} catch(e) {
console.error(e)
}
pub.setPrivateProp('you ok')
console.log('pub.getPrivateProp()', pub.getPrivateProp())
console.log('pub', Object.keys(pub))
这种方法的优点
私有属性访问验证被装饰在实例上(可选)。 私有属性可以在控制台、调试器和测试环境中检查,属性简单(没有符号或映射) 您可以控制验证和错误处理
的缺点
代理增加了开销和抽象级别 调试将显示包装对象的Proxy() 访问私有道具的方法需要是箭头函数 当无意中暴露实例时,可能会泄漏私有道具。添加一个方法getSelf = () => this
注:
考虑到开销,这种方法可以用于属性封装和调试的清晰度超过开销的场景。例如,当从存储中填充模型时。如。setjson (json)将确保没有私有道具被破坏。
通过使用WeakMap和Proxy来确保“私有”属性不可见,同时允许在每个作用域上使用相同实例访问WeakMap,可以进一步调整此方法以提供更好的封装。然而,这牺牲了可读性和调试。
其他回答
使用WeakMap可以在类中拥有私有方法。
根据MDN网络文档:
WeakMap对象是键/值对的集合,其中键仅为对象,值可以是任意值。 键中的对象引用是弱保存的,这意味着如果不再有对该对象的其他引用,则它们是垃圾收集(GC)的目标。
这是一个创建带有私有成员_items的Queue数据结构的例子,该成员_items包含一个数组。
const _items = new WeakMap();
class Queue {
constructor() {
_items.set(this, []);
}
enqueue( item) {
_items.get(this).push(item);
}
get count() {
return _items.get(this).length;
}
peek() {
const anArray = _items.get(this);
if( anArray.length == 0)
throw new Error('There are no items in array!');
if( anArray.length > 0)
return anArray[0];
}
dequeue() {
const anArray = _items.get(this);
if( anArray.length == 0)
throw new Error('There are no items in array!');
if( anArray.length > 0)
return anArray.splice(0, 1)[0];
}
}
使用的例子:
const c = new Queue();
c.enqueue("one");
c.enqueue("two");
c.enqueue("three");
c.enqueue("four");
c.enqueue("five");
console.log(c);
私有成员_items是隐藏的,不能在Queue对象的属性或方法中看到:
然而,Queue对象中的私有成员_items可以通过以下方式访问:
const anArray = _items.get(this);
事实上,使用符号和代理是可能的。您可以使用类作用域中的符号,并在代理中设置两个陷阱:一个用于类原型,以便Reflect.ownKeys(实例)或Object。getOwnPropertySymbols不会泄露你的符号,另一个是构造函数本身,所以当调用new ClassName(attrs)时,返回的实例将被拦截,并有自己的属性符号被阻塞。 代码如下:
const Human = (function() { const pet = Symbol(); const greet = Symbol(); const Human = privatizeSymbolsInFn(function(name) { this.name = name; // public this[pet] = 'dog'; // private }); Human.prototype = privatizeSymbolsInObj({ [greet]() { // private return 'Hi there!'; }, revealSecrets() { console.log(this[greet]() + ` The pet is a ${this[pet]}`); } }); return Human; })(); const bob = new Human('Bob'); console.assert(bob instanceof Human); console.assert(Reflect.ownKeys(bob).length === 1) // only ['name'] console.assert(Reflect.ownKeys(Human.prototype).length === 1 ) // only ['revealSecrets'] // Setting up the traps inside proxies: function privatizeSymbolsInObj(target) { return new Proxy(target, { ownKeys: Object.getOwnPropertyNames }); } function privatizeSymbolsInFn(Class) { function construct(TargetClass, argsList) { const instance = new TargetClass(...argsList); return privatizeSymbolsInObj(instance); } return new Proxy(Class, { construct }); }
Reflect.ownKeys()是这样工作的:Object.getOwnPropertyNames(myObj).concat(Object.getOwnPropertySymbols(myObj))这就是为什么我们需要为这些对象设置一个陷阱。
大多数答案要么说不可能,要么要求你使用WeakMap或Symbol,这是ES6的特性,可能需要腻子。然而,还有另一种方法!看看这个:
// 1. Create closure var SomeClass = function() { // 2. Create `key` inside a closure var key = {}; // Function to create private storage var private = function() { var obj = {}; // return Function to access private storage using `key` return function(testkey) { if(key === testkey) return obj; // If `key` is wrong, then storage cannot be accessed console.error('Cannot access private properties'); return undefined; }; }; var SomeClass = function() { // 3. Create private storage this._ = private(); // 4. Access private storage using the `key` this._(key).priv_prop = 200; }; SomeClass.prototype.test = function() { console.log(this._(key).priv_prop); // Using property from prototype }; return SomeClass; }(); // Can access private property from within prototype var instance = new SomeClass(); instance.test(); // `200` logged // Cannot access private property from outside of the closure var wrong_key = {}; instance._(wrong_key); // undefined; error logged
我称这个方法为accessor pattern。基本思想是我们有一个闭包,闭包中有一个键,我们创建一个私有对象(在构造函数中),只有当你拥有键时才能访问它。
如果你感兴趣,你可以在我的文章中阅读更多有关这方面的内容。使用此方法,可以为每个对象创建不能在闭包外部访问的属性。因此,你可以在构造函数或原型中使用它们,但不能在其他任何地方使用。我还没有在任何地方见过这种方法,但我认为它真的很强大。
我意识到这里有很多答案。我想分享我的解决方案,它确保了ES6类和旧JS中真正的私有变量。
var MyClass = (function() {
var $ = new WeakMap();
function priv(self) {
var r = $.get(self);
if (!r) $.set(self, r={});
return r;
}
return class { /* use priv(this).prop inside your class */ }
}();
外部世界无法访问$,这一事实确保了隐私。
当实例消失时,WeakMap将释放数据。
这肯定在纯Javascript中工作,我相信他们在ES6类中工作,但我还没有测试$将在成员方法的范围内可用。
实际上这是可能的。 1. 首先,创建类,并在构造函数中返回被调用的_public函数。 2. 在被调用的_public函数中传递this引用(以获得对所有私有方法和道具的访问),以及构造函数的所有参数(将在new Names()中传递) 3.在_public函数作用域中,还有一个Names类,它可以访问私有Names类的this (_this)引用
class Names {
constructor() {
this.privateProperty = 'John';
return _public(this, arguments);
}
privateMethod() { }
}
const names = new Names(1,2,3);
console.log(names.somePublicMethod); //[Function]
console.log(names.publicProperty); //'Jasmine'
console.log(names.privateMethod); //undefined
console.log(names.privateProperty); //undefind
function _public(_this, _arguments) {
class Names {
constructor() {
this.publicProperty = 'Jasmine';
_this.privateProperty; //"John";
_this.privateMethod; //[Function]
}
somePublicMethod() {
_this.privateProperty; //"John";
_this.privateMethod; //[Function]
}
}
return new Names(..._arguments);
}