JavaScript ES6类中的私有属性
是否可以在ES6类中创建私有属性 这里有一个例子。 如何阻止访问JavaScript ES6类中的私有属性,javascript,class,private-members,ecmascript-6,Javascript,Class,Private Members,Ecmascript 6,是否可以在ES6类中创建私有属性 这里有一个例子。 如何阻止访问实例.property class Something { constructor(){ this.property = "test"; } } var instance = new Something(); console.log(instance.property); //=> "test" 取决于:-) 没有private属性修饰符包含在中,该修饰符似乎已包含在中 然而,可能会有,这确实允许私有属性—
实例.property
class Something {
constructor(){
this.property = "test";
}
}
var instance = new Something();
console.log(instance.property); //=> "test"
取决于:-)
没有private
属性修饰符包含在中,该修饰符似乎已包含在中
然而,可能会有,这确实允许私有属性——它们可能也可以用于类定义中。更新:A正在进行中。欢迎捐款
是的,有-对于对象中的作用域访问- 符号是唯一的,除非使用反射(如Java/C#中的privates),否则无法从外部访问符号,但任何可以访问内部符号的人都可以将其用于密钥访问:
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
答案是“不”。但您可以创建对以下属性的私有访问:
import Something from './something.js';
const something = new Something('Sunny day!');
something.say();
something._message; // undefined
something._greet(); // exception
- 使用模块。模块中的所有内容都是私有的,除非通过使用
关键字将其公开export
- 在模块内部,使用函数闭包:
(关于可以使用符号来确保隐私的建议在早期版本的ES6规范中是正确的,但现在不再是这样了:和。有关符号和隐私的更多讨论,请参见:)对于其他旁观者的未来参考,我现在听说该建议是用于保存私人数据 下面是一个更清晰、更有效的示例:
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}`)
}
}
}
}
简而言之,不,ES6类对私有属性没有本机支持 但是您可以通过不将新属性附加到对象来模拟这种行为,而是将它们保留在类构造函数中,并使用getter和setter来访问隐藏的属性。注意,getter和setter在类的每个新实例上都得到重新定义 ES6 ES5
在JS中获得真正隐私的唯一方法是通过作用域,因此没有办法拥有一个属性,该属性是
this
的成员,只能在组件内部访问。在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是动态的。有人仍然可以这样做
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));
}
}
要扩展@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
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
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.
作用域弱映射
可以使用WeakMap来避免前面方法的性能和内存损失。WeakMap将数据与对象(这里是实例)相关联,使其只能使用该WeakMap进行访问。因此,我们使用scoped variables方法创建一个私有WeakMap,然后使用该WeakMap检索与此
关联的私有数据。这比scoped variables方法要快,因为您的所有实例都可以共享一个WeakMap,所以您不需要重新创建方法来让它们访问自己的WeakMap
例如:
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
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
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.
此示例使用一个对象将一个WeakMap用于多个私有属性;您还可以使用多个weakmap并使用它们,如age.set(this,20)
,或者编写一个小包装并以另一种方式使用,如privateProps.set(this,'age',0)
从理论上讲,篡改全局WeakMap
对象可能会侵犯此方法的隐私。也就是说,所有JavaScript都可能被损坏的全局文件破坏。我们的代码已经建立在假设这不会发生的基础上
(这种方法也可以通过Map
来实现,但是WeakMap
更好,因为Map
会造成内存泄漏,除非你非常小心,而且在这方面,两者没有什么不同。)
半回答:范围符号
符号是一种可以用作属性名称的基本值类型。您可以使用作用域变量方法创建专用符号,然后将专用数据存储在此[mySymbol]
中
使用Object.getOwnPropertySymbols
可以破坏此方法的隐私,但这样做有些尴尬
例如:
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
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
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.
一半答案:下划线
旧的默认设置是,只使用带有下划线前缀的公共属性。尽管在任何方面都不是私有财产,但这一惯例非常普遍,它很好地传达了读者应该将财产视为私有财产的信息,这通常会完成工作。作为这一失误的交换,我们得到了一种更容易阅读、更容易打字和更快的方法
例如:
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
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
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.
结论
截至2017年12月31日,仍然没有完善的私人地产开发方法。各种方法各有利弊。作用域变量是真正私有的;作用域的弱映射是非常私有的,比作用域变量更实用;限定范围的符号具有合理的私密性和实用性;下划线通常是私有的,并且非常实用。是的-您可以创建封装的属性,但访问修饰符(public | private)没有做到这一点,至少ES6没有做到这一点 下面是一个简单的例子,它是如何被定义的
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
new Animal("Cat").name; // Error: 'name' is private;
var Animal = (function () {
function Animal(theName) {
this.name = theName;
}
return Animal;
}());
console.log(new Animal("Cat").name);
function Private() {
const map = new WeakMap();
return obj => {
let props = map.get(obj);
if (!props) {
props = {};
map.set(obj, props);
}
return props;
};
}
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
}
}
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)}}}})();
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);
}
class Example {
constructor(foo) {
// privates
const self = this;
this.foo = foo;
// public interface
return self.public;
}
public = {
// empty data
nodata: { data: [] },
// noop
noop: () => {},
}
// everything else private
bar = 10
}
const test = new Example('FOO');
console.log(test.foo); // undefined
console.log(test.noop); // { data: [] }
console.log(test.bar); // undefined
class Countdown {
constructor(counter, action) {
Object.assign(this, {
dec() {
if (counter < 1) return;
counter--;
if (counter === 0) {
action();
}
}
});
}
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();
class Countdown {
constructor(counter, action) {
this.dec = function dec() {
if (counter < 1) return;
counter--;
if (counter === 0) {
action();
}
}
}
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();
class Countdown {
constructor(counter, action) {
this._counter = counter;
this._action = action;
}
dec() {
if (this._counter < 1) return;
this._counter--;
if (this._counter === 0) {
this._action();
}
}
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();
const _counter = new WeakMap();
const _action = new WeakMap();
class Countdown {
constructor(counter, action) {
_counter.set(this, counter);
_action.set(this, action);
}
dec() {
let counter = _counter.get(this);
if (counter < 1) return;
counter--;
_counter.set(this, counter);
if (counter === 0) {
_action.get(this)();
}
}
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();
const _counter = Symbol('counter');
const _action = Symbol('action');
class Countdown {
constructor(counter, action) {
this[_counter] = counter;
this[_action] = action;
}
dec() {
if (this[_counter] < 1) return;
this[_counter]--;
if (this[_counter] === 0) {
this[_action]();
}
}
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();
class Cat {
constructor(name ,age) {
this.name = name
this.age = age
Object.freeze(this)
}
}
let cat = new Cat('Garfield', 5)
cat.age = 6 // doesn't work, even throws an error in strict mode
class Something {
#property;
constructor(){
this.#property = "test";
}
#privateMethod() {
return 'hello world';
}
getPrivateMessage() {
return this.#property;
}
}
const instance = new Something();
console.log(instance.property); //=> undefined
console.log(instance.privateMethod); //=> undefined
console.log(instance.getPrivateMessage()); //=> test
class jobImpl{
// public
constructor(name){
this.name = name;
}
// public
do(time){
console.log(`${this.name} started at ${time}`);
this.prepare();
this.execute();
}
//public
stop(time){
this.finish();
console.log(`${this.name} finished at ${time}`);
}
// private
prepare(){ console.log('prepare..'); }
// private
execute(){ console.log('execute..'); }
// private
finish(){ console.log('finish..'); }
}
function Job(name){
var impl = new jobImpl(name);
return {
do: time => impl.do(time),
stop: time => impl.stop(time)
};
}
// Test:
// create class "Job"
var j = new Job("Digging a ditch");
// call public members..
j.do("08:00am");
j.stop("06:00pm");
// try to call private members or fields..
console.log(j.name); // undefined
j.execute(); // error
function Job(name){
var impl = new jobImpl(name);
this.do = time => impl.do(time),
this.stop = time => impl.stop(time)
}
const friend = Symbol('friend');
const ClassName = ((hidden, hiddenShared = 0) => {
class ClassName {
constructor(hiddenPropertyValue, prop){
this[hidden] = hiddenPropertyValue * ++hiddenShared;
this.prop = prop
}
get hidden(){
console.log('getting hidden');
return this[hidden];
}
set [friend](v){
console.log('setting hiddenShared');
hiddenShared = v;
}
get counter(){
console.log('getting hiddenShared');
return hiddenShared;
}
get privileged(){
console.log('calling privileged method');
return privileged.bind(this);
}
}
function privileged(value){
return this[hidden] + value;
}
return ClassName;
})(Symbol('hidden'), 0);
const OtherClass = (() => class OtherClass extends ClassName {
constructor(v){
super(v, 100);
this[friend] = this.counter - 1;
}
})();
class MyClass {
#privateProperty = 1
#privateMethod() { return 2 }
static #privateStatic = 3
static #privateStaticMethod(){return 4}
static get #privateStaticGetter(){return 5}
// also using is quite straightforward
method(){
return (
this.#privateMethod() +
this.#privateProperty +
MyClass.#privateStatic +
MyClass.#privateStaticMethod() +
MyClass.#privateStaticGetter
)
}
}
new MyClass().method()
// returns 15