JavaScript ES6类中的私有属性
是否有可能在ES6类中创build私有属性?
这是一个例子。 我怎样才能防止访问instance.property
?
class Something { constructor(){ this.property = "test"; } } var instance = new Something(); console.log(instance.property); //=> "test"
简单的回答,不,对ES6类的私有财产没有本地支持。
但是可以通过不将新属性附加到对象来模仿这种行为,而是将它们保存在类构造器中,并使用getter和setter来访问隐藏的属性。 请注意,getter和setter会在每个新的类的实例上重新定义。
ES6
class person { constructor(name) { var _name = name this.setName = function(name) { _name = name; } this.getName = function() { return _name; } } }
ES5
function person(name) { var _name = name this.setName = function(name) { _name = name; } this.getName = function() { return _name; } }
更新: 具有更好语法的提议正在进行中。 贡献值得欢迎。
是的,对于范围内的对象访问 – ES6引入了Symbol
。
符号是唯一的,除了reflection之外,你不能从外部访问一个(像Java / C#中的私有),但是有权访问内部符号的任何人都可以使用它来访问密钥:
var property = Symbol(); class Something { constructor(){ this[property] = "test"; } } var instance = new Something(); console.log(instance.property); //=> undefined, can only access with access to the Symbol
要扩展@ loganfsmyth的答案:
JavaScript中唯一真正的私有数据仍然是范围variables。 您不能像在公共属性中那样在内部访问属性的意义上使用私有属性,但是可以使用作用域variables来存储私有数据。
范围variables
这里的方法是使用私有的构造函数的范围来存储私人数据。 对于访问这个私有数据的方法,它们也必须在构造函数中创build,这意味着你正在用每个实例重新创build它们。 这是一个performance和记忆的惩罚,但有些人认为罚则是可以接受的。 对于不需要访问私人数据的方法,通过将其添加到原型中,可以避免这种惩罚。
例:
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
WeakMap可以用来避免以前的方法的性能和内存的损失。 WeakMaps将数据与对象(这里是实例)关联起来,只能使用该WeakMap
进行访问。 因此,我们使用范围variables方法来创build一个私有WeakMap,然后使用该WeakMap来检索与this
相关的私有数据。 这比范围variables方法快,因为你所有的实例可以共享一个WeakMap,所以你不需要重新创build方法,以便他们都可以访问自己的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; 你也可以使用多个WeakMaps并像age.set(this, 20)
一样使用它们,或者写一个小包装器,并以另一种方式使用它,比如privateProps.set(this, 'age', 0)
。
(这个方法也可以用Map
来完成,但是WeakMap
更好,因为Map
会创build内存泄漏,除非你非常小心,为此目的,两者没有其他不同。)
半答案
不幸的是,JavaScript是一种半答案通常比真正答案更容易获得的语言。
WeakMap方法在技术上只是一个半答案,因为有人可能会篡改全局WeakMap
对象,但我认为它比其他答案要less,因为所有的JavaScript代码都可以被破坏的全局variables所破坏。 这只是我们需要承担可信的代码不会做的事情。
范围的符号
符号是一种可以用作属性名称的原始值。 您可以使用作用域variables方法来创build一个私有的Symbol,然后在this[mySymbol]
处存储私有数据。 这是一个半答案,因为对象的符号键可以通过外部代码列出。 尽pipe如此,以下是如何使用符号:
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.
结论
不足之处在于,私有财产不是ES2015固定的东西,而只是摆弄而已。 所以,事情的状态是有一个选项清单,没有一个是完全优越的,每个选项都有优点和缺点。 对不起。
答案是不”。 但是你可以创build像这样的属性的私人访问:
- 使用模块。 除非使用
export
关键字公开它,否则模块中的所有内容都是私有的。 - 在模块内部,使用函数closures: http : //www.kirupa.com/html5/closures_in_javascript.htm
(在早期版本的ES6规范中,可以使用符号来确保隐私的build议,但不再是这种情况: https : //mail.mozilla.org/pipermail/es-discuss/2014-January/035604。 html和https://stackoverflow.com/a/22280202/1282216 。有关符号和隐私的更长时间的讨论,请参阅: https : //curiosity-driven.org/private-properties-in-javascript )
在JS中获得真正隐私的唯一方法是通过范围确定,所以没有办法拥有属于这个属性的属性,只能在组件内访问属性。 在ES6中存储真正私人数据的最好方法是使用WeakMap。
const privateProp1 = new WeakMap(); const privateProp2 = new WeakMap(); class SomeClass { constructor() { privateProp1.set(this, "I am Private1"); privateProp2.set(this, "I am Private2"); this.publicVar = "I am public"; this.publicMethod = () => { console.log(privateProp1.get(this), privateProp2.get(this)) }; } printPrivate() { console.log(privateProp1.get(this)); } }
显然,这可能很慢,而且确实很丑,但它确实提供了隐私。
请记住,即使这是不完美的,因为JavaScript是如此的dynamic。 有人还可以做
var oldSet = WeakMap.prototype.set; WeakMap.prototype.set = function(key, value){ // Store 'this', 'key', and 'value' return oldSet.call(this, key, value); };
捕获存储的值,所以如果你想要特别小心,你需要捕获一个本地引用.set
和.get
来显式使用,而不是依赖于可重写的原型。
const {set: WMSet, get: WMGet} = WeakMap.prototype; const privateProp1 = new WeakMap(); const privateProp2 = new WeakMap(); class SomeClass { constructor() { WMSet.call(privateProp1, this, "I am Private1"); WMSet.call(privateProp2, this, "I am Private2"); this.publicVar = "I am public"; this.publicMethod = () => { console.log(WMGet.call(privateProp1, this), WMGet.call(privateProp2, this)) }; } printPrivate() { console.log(WMGet.call(privateProp1, this)); } }
为了将来的其他用户的参考,我现在听到的build议是使用WeakMaps来保存私人数据。
这是一个更清晰的工作示例:
function storePrivateProperties(a, b, c, d) { let privateData = new WeakMap; // unique object as key, weak map can only accept object as key, when key is no longer referened, garbage collector claims the key-value let keyA = {}, keyB = {}, keyC = {}, keyD = {}; privateData.set(keyA, a); privateData.set(keyB, b); privateData.set(keyC, c); privateData.set(keyD, d); return { logPrivateKey(key) { switch(key) { case "a": console.log(privateData.get(keyA)); break; case "b": console.log(privateData.get(keyB)); break; case "c": console.log(privateData.get(keyC)); break; case "d": console.log(privateData.set(keyD)); break; default: console.log(`There is no value for ${key}`) } } } }
取决于你问的人 🙂
最小最小类别提案中没有包含任何private
属性修饰符,似乎已将其纳入当前草案 。
但是,可能会支持 专用名称 ,它允许私有属性 – 而且它们也可以用于类定义。
完成@ d13和@ johnny-oshika和@DanyalAytekin的评论:
我想在@ johnny-oshika提供的例子中,我们可以使用普通函数代替箭头函数,然后将它们与当前对象绑定,再加上一个_privates
对象作为curried参数:
something.js
function _greet(_privates) { return 'Hello ' + _privates.message; } function _updateMessage(_privates, newMessage) { _privates.message = newMessage; } export default class Something { constructor(message) { const _privates = { message }; this.say = _greet.bind(this, _privates); this.updateMessage = _updateMessage.bind(this, _privates); } }
main.js
import Something from './something.js'; const something = new Something('Sunny day!'); const message1 = something.say(); something.updateMessage('Cloudy day!'); const message2 = something.say(); console.log(message1 === 'Hello Sunny day!'); // true console.log(message2 === 'Hello Cloudy day!'); // true // the followings are not public console.log(something._greet === undefined); // true console.log(something._privates === undefined); // true console.log(something._updateMessage === undefined); // true // another instance which doesn't share the _privates const something2 = new Something('another Sunny day!'); const message3 = something2.say(); console.log(message3 === 'Hello another Sunny day!'); // true
我能想到的好处:
- 我们可以有私有方法(
_greet
和_updateMessage
像私有方法一样,只要我们不export
引用) - 虽然它们不在原型上面,但是上面提到的方法会节省内存,因为实例是在类之外创build的(而不是在构造函数中定义它们)
- 我们不会泄漏任何全局variables,因为我们在模块中
- 我们也可以使用绑定
_privates
对象的私有属性
我能想到的一些缺点:
- 不太直观
- 类语法和老派模式的混合使用(对象绑定,模块/函数范围variables)
- 硬绑定 – 我们不能重新绑定公共方法(尽pipe我们可以通过使用软绑定来改善这一点( https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26% 20对象%20原型/ ch2.md#softening-binding ))
正在运行的代码片段可以在这里find: http : //www.webpackbin.com/NJgI5J8lZ
使用ES6模块(最初由@ d13提议)适合我。 它并不完全模仿私人财产,但至less你可以确信,应该是私人的财产不会泄漏到你的class级之外。 这是一个例子:
something.js
let _message = null; const _greet = name => { console.log('Hello ' + name); }; export default class Something { constructor(message) { _message = message; } say() { console.log(_message); _greet('Bob'); } };
然后消费代码可以看起来像这样:
import Something from './something.js'; const something = new Something('Sunny day!'); something.say(); something._message; // undefined something._greet(); // exception
更新(重要):
正如@DanyalAytekin在评论中所概述的那样,这些私有属性是静态的,因此是全局的。 在使用Singleton时,他们会很好的工作,但是对于Transient对象必须小心。 扩展上面的例子:
import Something from './something.js'; import Something2 from './something.js'; const a = new Something('a'); a.say(); // a const b = new Something('b'); a.say(); // a const c = new Something2('c'); c.say(); // c a.say(); // c b.say(); // c c.say(); // c
是的 – 你可以创build封装的属性 ,但至less没有用ES6访问修饰符(public | private)。
这里是一个简单的例子,它是如何使用ES6完成的:
1使用类字创build类
2在它的构造函数中使用let或const保留字声明块范围variables – >因为它们是块范围,所以不能从外部访问(封装)
3为了允许一些访问控制(setters | getters)给这些variables,你可以在它的构造this.methodName=function(){}
声明实例方法: this.methodName=function(){}
syntax
"use strict"; class Something{ constructor(){ //private property let property="test"; //private final (immutable) property const property2="test2"; //public getter this.getProperty2=function(){ return property2; } //public getter this.getProperty=function(){ return property; } //public setter this.setProperty=function(prop){ property=prop; } } }
现在让我们检查一下:
var s=new Something(); console.log(typeof s.property);//undefined s.setProperty("another");//set to encapsulated `property` console.log(s.getProperty());//get encapsulated `property` value console.log(s.getProperty2());//get encapsulated immutable `property2` value
我相信可以在构造函数中使用闭包来获得“两全其美”。 有两个变化:
所有数据成员都是私有的
function myFunc() { console.log('Value of x: ' + this.x); this.myPrivateFunc(); } function myPrivateFunc() { console.log('Enhanced value of x: ' + (this.x + 1)); } class Test { constructor() { let internal = { x : 2, }; internal.myPrivateFunc = myPrivateFunc.bind(internal); this.myFunc = myFunc.bind(internal); } };
实际上它可能使用符号和代理。 您可以使用类作用域中的符号,并在代理中设置两个陷阱:一个用于类原型,以便Reflect.ownKeys(instance)或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 }); }
我个人喜欢绑定操作符 ::
的build议,然后将它与提到的解决scheme@ d13结合起来,但是现在坚持@ d13的答案,在你的类中使用export
关键字并将私有函数放在模块中。
还有一个解决难题的方法,这里没有提到,下面是更实用的方法,并且允许它在课堂上拥有所有的私人道具/方法。
Private.js
export const get = state => key => state[key]; export const set = state => (key,value) => { state[key] = value; }
Test.js
import { get, set } from './utils/Private' export default class Test { constructor(initialState = {}) { const _set = this.set = set(initialState); const _get = this.get = get(initialState); this.set('privateMethod', () => _get('propValue')); } showProp() { return this.get('privateMethod')(); } } let one = new Test({ propValue: 5}); let two = new Test({ propValue: 8}); two.showProp(); // 8 one.showProp(); // 5
对此意见,将不胜感激。
我认为本杰明的答案可能是大多数情况下最好的答案 ,直到语言本身支持明确的私有variables。
但是,如果由于某种原因需要使用Object.getOwnPropertySymbols()
来阻止访问,我所考虑使用的方法是附加一个唯一的,不可configuration的,不可枚举的,不可写的属性,该属性可以用作属性标识符到每一个对象的build设(如独特的Symbol
,如果你还没有其他一些独特的财产,如id
)。 然后,使用该标识符保存每个对象的“私有”variables的映射。
const privateVars = {}; class Something { constructor(){ Object.defineProperty(this, '_sym', { configurable: false, enumerable: false, writable: false, value: Symbol() }); var myPrivateVars = { privateProperty: "I'm hidden" }; privateVars[this._sym] = myPrivateVars; this.property = "I'm public"; } getPrivateProperty() { return privateVars[this._sym].privateProperty; } } var instance = new Something(); console.log(instance.property); //=> "I'm public" console.log(instance.privateProperty); //=> undefined console.log(instance.getPrivateProperty()); //=> "I'm hidden"
如果性能成为问题,这种方法相对于使用WeakMap
的潜在优势是更快的访问时间 。
class Something { constructor(){ var _property = "test"; Object.defineProperty(this, "property", { get: function(){ return _property} }); } } var instance = new Something(); console.log(instance.property); //=> "test" instance.property = "can read from outside, but can't write"; console.log(instance.property); //=> "test"
符号
- 在IE11中不受支持
- 私有variables可以通过
getOwnPropertySymbols
find - 需要为每个私有variables创build一个新的Symbol
closures
- 在任何js引擎中工作
- 私有variables在对象上不可访问
- 需要在构造函数中包含所有公共方法,或者将私有引用对象绑定到公共方法
WeakMap
- 支持IE11
- 私有variables在对象上不可访问
- 可以看起来很干净
首先定义一个函数来包装WeakMap:
function Private() { const map = new WeakMap(); return obj => { let props = map.get(obj); if (!props) { props = {}; map.set(obj, props); } return props; }; }
然后,在你的课堂外build立一个参考:
const p = new Private(); class Person { constructor(name, age) { this.name = name; p(this).age = age; // it's easy to set a private variable } getAge() { return p(this).age; // and get a private variable } }
Note: class isn't supported by IE11, but it looks cleaner in the example.
Coming very late to this party but I hit the OP question in a search so… Yes, you can have private properties by wrapping the class declaration in a closure
There is an example of how I have private methods in this codepen . In the snippet below, the Subscribable class has two 'private' functions process
and processCallbacks
. Any properties can be added in this manner and they are kept private through the use of the closure. IMO Privacy is a rare need if concerns are well separated and Javascript does not need to become bloated by adding more syntax when a closure neatly does the job.
const Subscribable = (function(){ const process = (self, eventName, args) => { self.processing.set(eventName, setTimeout(() => processCallbacks(self, eventName, args)))}; const processCallbacks = (self, eventName, args) => { if (self.callingBack.get(eventName).length > 0){ const [nextCallback, ...callingBack] = self.callingBack.get(eventName); self.callingBack.set(eventName, callingBack); process(self, eventName, args); nextCallback(...args)} else { delete self.processing.delete(eventName)}}; return class { constructor(){ this.callingBack = new Map(); this.processing = new Map(); this.toCallbacks = new Map()} subscribe(eventName, callback){ const callbacks = this.unsubscribe(eventName, callback); this.toCallbacks.set(eventName, [...callbacks, callback]); return () => this.unsubscribe(eventName, callback)} // callable to unsubscribe for convenience unsubscribe(eventName, callback){ let callbacks = this.toCallbacks.get(eventName) || []; callbacks = callbacks.filter(subscribedCallback => subscribedCallback !== callback); if (callbacks.length > 0) { this.toCallbacks.set(eventName, callbacks)} else { this.toCallbacks.delete(eventName)} return callbacks} emit(eventName, ...args){ this.callingBack.set(eventName, this.toCallbacks.get(eventName) || []); if (!this.processing.has(eventName)){ process(this, eventName, args)}}}})();
I like this approach because it separates concerns nicely and keeps things truly private. The only downside is the need to use 'self' (or something similar) to refer to 'this' in the private content.
A different approach to "private"
Instead of fighting against the fact that private visibility is currently unavailable in ES6, I decided to take a more practical approach that does just fine if your IDE supports JSDoc (eg, Webstorm). The idea is to use the @private
tag . As far as development goes, the IDE will prevent you from accessing any private member from outside its class. Works pretty well for me and it's been really useful for hiding internal methods so the auto-complete feature shows me just what the class really meant to expose. 这是一个例子:
Even Typescript can't do it. From their documentation :
When a member is marked private, it cannot be accessed from outside of its containing class. 例如:
class Animal { private name: string; constructor(theName: string) { this.name = theName; } } new Animal("Cat").name; // Error: 'name' is private;
But transpiled on their playground this gives:
var Animal = (function () { function Animal(theName) { this.name = theName; } return Animal; }()); console.log(new Animal("Cat").name);
So their "private" keyword is ineffective.
See this answer for aa clean & simple 'class' solution with a private and public interface and support for composition
Yes totally can, and pretty easily too. This is done by exposing your private variables and functions by returning the prototype object graph in the constructor. This is nothing new, but take a bit of js foo to understand the elegance of it. This way does not use global scoped, or weakmaps. It is a form of reflection built into the language. Depending on how you leverage this; one can either force an exception which interrupts the call stack, or bury the exception as an undefined
. This is demonstarted below, and can read more about these features here
class Clazz { constructor() { var _level = 1 function _private(x) { return _level * x; } return { level: _level, public: this.private, public2: function(x) { return _private(x); }, public3: function(x) { return _private(x) * this.public(x); }, }; } private(x) { return x * x; } } var clazz = new Clazz(); console.log(clazz._level); //undefined console.log(clazz._private); // undefined console.log(clazz.level); // 1 console.log(clazz.public(1)); //2 console.log(clazz.public2(2)); //2 console.log(clazz.public3(3)); //27 console.log(clazz.private(0)); //error
Most answers either say it's impossible, or require you to use a WeakMap or Symbol, which are ES6 features that would probably require polyfills. There's however another way! Check out this out:
// 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
Actually it is possible.
class Person{ constructor(name,age){ class Person{ constructor(_private){ this.name = name; this.age = age; this.isHuman = _private.human; this.greet = _private.greeting.call(this); //without 'call' it takes _private object as 'this' keyword } } //private properties here this.human = true; return new Person(this); } //private methods here greeting(){ return `hello ${this.name}`; } } var person = new Person('Paul',27); console.log(person.name); //'Paul' console.log(person.age); //27 console.log(person.isHuman); //true console.log(person.greet); //'Hello Paul' console.log(person.human); //undefined (private) console.log(person.greeting); //undefined (private)