Ist es möglich, private Eigenschaften in ES6-Klassen zu erstellen?
Hier ein Beispiel ... Wie kann ich den Zugriff auf instance.property
verhindern?
class Something {
constructor(){
this.property = "test";
}
}
var instance = new Something();
console.log(instance.property); //=> "test"
Private Felder werden im ECMA-Standard implementiert. Sie können sie heute mit babel 7 und Stage 3 Preset verwenden. Siehe babel REPL Beispiel .
class Something {
#property;
constructor(){
this.#property = "test";
}
}
const instance = new Something();
console.log(instance.property); //=> undefined
Kurze Antwort, nein, es gibt keine native Unterstützung für private Immobilien mit ES6-Klassen.
Sie können dieses Verhalten jedoch nachahmen, indem Sie die neuen Eigenschaften nicht an das Objekt anhängen, sondern sie innerhalb eines Klassenkonstruktors belassen und mithilfe von Gettern und Setters die verborgenen Eigenschaften erreichen. Beachten Sie, dass die Getter und Setter in jeder neuen Instanz der Klasse neu definiert werden.
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; }
}
Um die Antwort von @ loganfsmyth zu erweitern:
Die einzigen wirklich privaten Daten in JavaScript sind noch Variablen im Gültigkeitsbereich. Sie können keine privaten Eigenschaften im Sinne von Eigenschaften haben, auf die intern wie öffentliche Eigenschaften zugegriffen wird. Sie können jedoch Bereichsvariablen verwenden, um private Daten zu speichern.
Der Ansatz hier ist, den Umfang der Konstruktorfunktion, die privat ist, zum Speichern privater Daten zu verwenden. Damit Methoden auf diese privaten Daten zugreifen können, müssen sie auch im Konstruktor erstellt werden. Das bedeutet, dass Sie sie mit jeder Instanz neu erstellen. Dies ist eine Leistungs- und Speicherstrafe, aber einige glauben, dass die Strafe akzeptabel ist. Die Strafe kann für Methoden vermieden werden, die keinen Zugriff auf private Daten benötigen, indem sie wie üblich zum Prototyp hinzugefügt werden.
Beispiel:
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
Eine WeakMap kann verwendet werden, um die Leistung und den Speichernachteil des vorherigen Ansatzes zu vermeiden. WeakMaps verknüpfen Daten mit Objekten (hier Instanzen) auf eine solche Weise, dass nur mit dieser WeakMap auf sie zugegriffen werden kann. Daher verwenden wir die Methode der Bereichsvariablen, um eine private WeakMap zu erstellen, und dann diese WeakMap, um private Daten abzurufen, die this
zugeordnet sind. Dies ist schneller als die Variablenvariablenmethode, da alle Ihre Instanzen eine einzelne WeakMap gemeinsam nutzen können. Sie müssen also keine Methoden neu erstellen, um nur auf ihre eigenen WeakMaps zugreifen zu können.
Beispiel:
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
In diesem Beispiel wird ein Objekt verwendet, um eine WeakMap für mehrere private Eigenschaften zu verwenden. Sie können auch mehrere WeakMaps verwenden und wie age.set(this, 20)
verwenden, oder einen kleinen Wrapper schreiben und ihn auf andere Weise verwenden, wie privateProps.set(this, 'age', 0)
.
Die Vertraulichkeit dieses Ansatzes könnte theoretisch durch Manipulation des globalen WeakMap
-Objekts verletzt werden. Das heißt, alle JavaScript können durch verstümmelte Globals gebrochen werden. Unser Code basiert bereits auf der Annahme, dass dies nicht geschieht.
(Diese Methode kann auch mit Map
ausgeführt werden, aber WeakMap
ist besser, da Map
Speicherverluste verursacht, wenn Sie nicht sehr vorsichtig sind und zu diesem Zweck die beiden nicht anders sind.)
Ein Symbol ist ein Typ von Grundwerten, der als Eigenschaftsname dienen kann. Sie können die Bereichsvariablenmethode verwenden, um ein privates Symbol zu erstellen und dann private Daten unter this[mySymbol]
zu speichern.
Die Vertraulichkeit dieser Methode kann mit Object.getOwnPropertySymbols
verletzt werden, ist aber etwas umständlich.
Beispiel:
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)`.
Bei der alten Standardeinstellung verwenden Sie einfach eine öffentliche Eigenschaft mit einem Unterstrich-Präfix. Obwohl dies in keiner Weise ein privates Eigentum ist, ist diese Konvention so weit verbreitet, dass sie gute Arbeit leistet, indem sie mitteilt, dass der Leser das Eigentum als privat behandeln sollte, was die Arbeit oft erledigt. Im Gegenzug für dieses Versäumnis erhalten wir einen Ansatz, der einfacher zu lesen, einfacher zu schreiben und schneller ist.
Beispiel:
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.
Ab ES2017 gibt es noch keine perfekte Möglichkeit, private Immobilien zu betreiben. Verschiedene Ansätze haben Vor- und Nachteile. Bereichsvariablen sind wirklich privat; Bereichs-WeakMaps sind sehr privat und praktischer als Bereichsvariablen. Symbolsymbole sind recht privat und praktisch praktisch; Unterstriche sind oft privat und sehr praktisch.
Update: Ein Vorschlag mit einer schöneren Syntax ist unterwegs. Beiträge sind willkommen.
Ja, es gibt - für den beschränkten Zugriff auf Objekte - ES6 führt Symbol
s ein.
Symbole sind einzigartig, Sie können von außen keinen Zugriff auf ein Symbol erhalten, außer durch Reflektion (wie bei Privaten in Java/C #). Jeder, der Zugriff auf ein Symbol im Inneren hat, kann es für den Schlüsselzugriff verwenden:
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
Die Antwort ist nein". Sie können jedoch einen privaten Zugriff auf folgende Eigenschaften erstellen:
export
veröffentlicht.(Der Vorschlag, dass Symbole verwendet werden könnten, um den Datenschutz sicherzustellen, war in einer früheren Version der ES6-Spezifikation wahr, trifft jedoch nicht mehr zu: https://mail.mozilla.org/pipermail/es-discuss/2014-January/ 035604.html und https://stackoverflow.com/a/22280202/1282216 Für eine längere Diskussion über Symbole und Datenschutz siehe: https://curiosity-driven.org/private-properties- in-javascript )
Der einzige Weg, um echte Privatsphäre in JS zu erhalten, ist das Scoping. Daher gibt es keine Möglichkeit, über eine Eigenschaft zu verfügen, die ein Mitglied von this
ist und auf die nur innerhalb der Komponente zugegriffen werden kann. Um wirklich private Daten in ES6 zu speichern, verwenden Sie eine 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));
}
}
Offensichtlich ist dies wahrscheinlich langsam und definitiv hässlich, aber es bietet Privatsphäre.
Denken Sie daran, dass EVEN THIS nicht perfekt ist, weil Javascript so dynamisch ist. Jemand könnte noch etwas tun
var oldSet = WeakMap.prototype.set;
WeakMap.prototype.set = function(key, value){
// Store 'this', 'key', and 'value'
return oldSet.call(this, key, value);
};
wenn Sie die Werte beim Speichern abfangen möchten, müssen Sie einen lokalen Verweis auf .set
und .get
aufzeichnen, der explizit verwendet werden soll, anstatt sich auf den überschreibbaren Prototyp zu verlassen.
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));
}
}
Zur späteren Bezugnahme auf andere Betrachter höre ich jetzt, dass es empfohlen wird, WeakMaps zu verwenden, um private Daten zu speichern.
Hier ist ein klareres Arbeitsbeispiel:
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}`)
}
}
}
}
Kommt drauf an wen Sie fragen :-)
Im Maximally Minimal-Klassen - Vorschlag ist kein private
-Eigenschaftsmodifikator enthalten der scheinbar in den aktuellen Entwurf aufgenommen wurde.
Es kann jedoch eine Unterstützung fürprivate names geben, die private Eigenschaften zulässt - und sie könnten wahrscheinlich auch in Klassendefinitionen verwendet werden.
Fertigstellung von @ d13 und Kommentaren von @ johnny-oshika und @DanyalAytekin:
In dem von @ johnny-oshika bereitgestellten Beispiel könnten wir normale Funktionen anstelle von Pfeilfunktionen verwenden und dann .bind
mit dem aktuellen Objekt plus einem _privates
-Objekt als Curry-Parameter:
etwas.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
Vorteile, die mir einfallen:
_greet
und _updateMessage
verhalten sich wie private Methoden, solange wir die Referenzen nicht export
verwenden) _privates
-Objekt habenEinige Nachteile die ich mir vorstellen kann:
Ein laufendes Snippet ist hier zu finden: http://www.webpackbin.com/NJgI5J8lZ
Ja - Sie können eine gekapselte Eigenschaft erstellen. Dies wurde jedoch nicht mit Zugriffsmodifizierern (public | private) vorgenommen, zumindest nicht mit ES6.
Hier ein einfaches Beispiel, wie es mit ES6 gemacht werden kann:
1 Erstellen Sie eine Klasse mit class Word
2 Innerhalb des Konstruktors deklarieren Sie die Blockbereichsvariable mit let OR const Reservierte Wörter ->, da es sich um Blockbereiche handelt, auf die kein Zugriff von außen möglich ist (gekapselt).
3 Um diesen Variablen Zugriff auf die Zugriffssteuerung (Setter | Getters) zu gewähren, können Sie die Instanzmethode innerhalb ihres Konstruktors deklarieren, indem Sie die this.methodName=function(){}
-Syntax verwenden
"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;
}
}
}
Jetzt können wir es überprüfen:
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
Die Verwendung von ES6-Modulen (ursprünglich von @ d13 vorgeschlagen) funktioniert für mich gut. Private Immobilien werden nicht perfekt nachgeahmt, aber Sie können zumindest sicher sein, dass Eigenschaften, die privat sein sollten, nicht außerhalb Ihrer Klasse laufen. Hier ist ein Beispiel:
let _message = null;
const _greet = name => {
console.log('Hello ' + name);
};
export default class Something {
constructor(message) {
_message = message;
}
say() {
console.log(_message);
_greet('Bob');
}
};
Dann kann der verbrauchende Code folgendermaßen aussehen:
import Something from './something.js';
const something = new Something('Sunny day!');
something.say();
something._message; // undefined
something._greet(); // exception
Wie in den Kommentaren von @DanyalAytekin beschrieben, sind diese privaten Eigenschaften statisch, also global. Sie funktionieren gut, wenn Sie mit Singletons arbeiten, aber auf transiente Objekte muss geachtet werden. Das obige Beispiel erweitern:
import Something from './something.js';
import Something2 from './something.js';
const a = new Something('a');
a.say(); // a
const b = new Something('b');
b.say(); // b
const c = new Something2('c');
c.say(); // c
a.say(); // c
b.say(); // c
c.say(); // c
Anstatt gegen die Tatsache vorzugehen, dass private Sichtbarkeit in ES6 derzeit nicht verfügbar ist, habe ich mich für einen praktischeren Ansatz entschieden, der gut funktioniert, wenn Ihre IDE JSDoc (z. B. Webstorm) unterstützt. Die Idee ist die Verwendung des @private
-Tags . In Bezug auf die Entwicklung hindert die IDE Sie daran, auf private Mitglieder von außerhalb ihrer Klasse zuzugreifen. Funktioniert ziemlich gut für mich und es ist wirklich nützlich, um interne Methoden auszublenden. Die Funktion zum automatischen Vervollständigen zeigt mir, was die Klasse wirklich zeigen wollte. Hier ist ein Beispiel:
WeakMap
Object.getOwnPropertySymbols
Soft-Private)Definieren Sie zunächst eine Funktion zum Umbrechen von WeakMap:
function Private() {
const map = new WeakMap();
return obj => {
let props = map.get(obj);
if (!props) {
props = {};
map.set(obj, props);
}
return props;
};
}
Erstellen Sie dann eine Referenz außerhalb Ihrer Klasse:
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
}
}
Hinweis: class wird nicht von IE11 unterstützt, im Beispiel sieht es jedoch sauberer aus.
Ich denke, Benjamins Antwort ist wahrscheinlich für die meisten Fälle die beste Lösung, bis die Sprache explizit private Variablen unterstützt.
Wenn Sie jedoch aus irgendeinem Grund den Zugriff mit Object.getOwnPropertySymbols()
verhindern müssen, muss eine von mir in Betracht gezogene Methode eine eindeutige, nicht konfigurierbare, nicht aufzählbare, nicht beschreibbare Eigenschaft enthalten, die als Eigenschafts-ID verwendet werden kann zu jedem Objekt in der Konstruktion (z. B. eine eindeutige Symbol
, wenn Sie noch keine andere eindeutige Eigenschaft wie eine id
haben). Behalten Sie dann einfach eine Zuordnung der "privaten" Variablen jedes Objekts mit diesem Bezeichner.
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;
}
// A clean up method of some kind is necessary since the
// variables won't be cleaned up from memory automatically
// when the object is garbage collected
destroy() {
delete privateVars[this._sym];
}
}
var instance = new Something();
console.log(instance.property); //=> "I'm public"
console.log(instance.privateProperty); //=> undefined
console.log(instance.getPrivateProperty()); //=> "I'm hidden"
Der potenzielle Vorteil dieses Ansatzes gegenüber der Verwendung einer WeakMap
ist eine schnellere Zugriffszeit wenn die Leistung zum Problem wird.
Ich persönlich mag den Vorschlag des bind-Operators::
und würde ihn dann mit der erwähnten Lösung @d13 kombinieren. Bleib aber vorerst bei @d13s Antwort, wo du das export
-Schlüsselwort für deine Klasse verwendest und die privaten Funktionen einfügst das Modul.
es gibt noch eine weitere schwierige Lösung, die hier nicht erwähnt wurde. Im Folgenden handelt es sich um einen funktionaleren Ansatz, der es ermöglichen würde, alle privaten Requisiten/Methoden innerhalb der Klasse zu haben.
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
kommentare dazu wären dankbar.
Ich glaube, es ist möglich, das Beste aus beiden Welten zu erhalten, indem Sie innerhalb von Konstruktoren Verschlüsse verwenden. Es gibt zwei Varianten:
Alle Datenmitglieder sind privat
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);
}
};
Einige Mitglieder sind privat
HINWEIS: Dies ist zugegebenermaßen hässlich. Wenn Sie eine bessere Lösung kennen, bearbeiten Sie diese Antwort.
function myFunc(priv, pub) {
pub.y = 3; // The Test object now gets a member 'y' with value 3.
console.log('Value of x: ' + priv.x);
this.myPrivateFunc();
}
function myPrivateFunc() {
pub.z = 5; // The Test object now gets a member 'z' with value 3.
console.log('Enhanced value of x: ' + (priv.x + 1));
}
class Test {
constructor() {
let self = this;
let internal = {
x : 2,
};
internal.myPrivateFunc = myPrivateFunc.bind(null, internal, self);
this.myFunc = myFunc.bind(null, internal, self);
}
};
In der Tat ist es möglich, Symbole und Proxies zu verwenden. Sie verwenden die Symbole im Klassenbereich und setzen zwei Traps in einem Proxy: eine für den Prototyp der Klasse, sodass die Reflect.ownKeys (Instanz) oder Object.getOwnPropertySymbols Ihre Symbole nicht verraten, die andere für den Konstruktor selbst Wenn also new ClassName(attrs)
aufgerufen wird, wird die zurückgegebene Instanz abgefangen und die eigenen Eigenschaftssymbole werden blockiert .. _ Hier ist der Code:
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()
funktioniert so: Object.getOwnPropertyNames(myObj).concat(Object.getOwnPropertySymbols(myObj))
Deshalb brauchen wir eine Falle für diese Objekte.
Ich bin auf diesen Beitrag gestoßen, als ich nach den besten Methoden für "private Daten für Klassen" gesucht habe. Es wurde erwähnt, dass einige der Muster Leistungsprobleme haben würden.
Ich habe ein paar Jsperf-Tests zusammengestellt, die auf den 4 Hauptmustern des Online-Buches "Exploring ES6" basieren:
http://exploringjs.com/es6/ch_classes.html#sec_private-data-for-classes
Die Tests finden Sie hier:
https://jsperf.com/private-data-for-classes
In Chrome 63.0.3239/Mac OS X 10.11.6 waren die besten Muster "Private Daten über Konstruktorumgebungen" und "Private Daten über eine Namenskonvention". Für mich lief Safari für WeakMap gut, aber Chrome nicht so gut.
Ich kenne die Auswirkungen auf den Speicher nicht, aber das Muster für "Konstruktorumgebungen", von dem einige gewarnt hatten, wäre ein Leistungsproblem, das sehr leistungsfähig war.
Die 4 Grundmuster sind:
Private Daten über Konstruktorumgebungen
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();
Private Daten über Konstruktorumgebungen 2
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();
Private Daten über eine Namenskonvention
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();
Private Daten über WeakMaps
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();
Private Daten über Symbole
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 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"
Sogar TypeScript kann das nicht. Aus ihrer Dokumentation :
Wenn ein Mitglied als privat markiert ist, kann nicht von außerhalb seiner Klasse aus auf ihn zugegriffen werden. Zum Beispiel:
class Animal { private name: string; constructor(theName: string) { this.name = theName; } } new Animal("Cat").name; // Error: 'name' is private;
Aber auf ihrem Spielplatz das gibt:
var Animal = (function () {
function Animal(theName) {
this.name = theName;
}
return Animal;
}());
console.log(new Animal("Cat").name);
Ihr "privates" Schlüsselwort ist also unwirksam.
Ich komme sehr spät zu dieser Gruppe, aber ich habe die OP-Frage bei einer Suche getroffen, also ...Ja, Sie können private Eigenschaften haben, indem Sie die Klassendeklaration in eine Schließung einschließen.
Es gibt ein Beispiel, wie ich private Methoden in this codepen habe. Im folgenden Snippet verfügt die Subscribable-Klasse über zwei "private" Funktionen process
und processCallbacks
. Alle Eigenschaften können auf diese Weise hinzugefügt werden und werden durch die Verwendung des Verschlusses geheim gehalten. IMO-Datenschutz ist ein seltenes Bedürfnis, wenn Bedenken gut voneinander getrennt sind und Javascript nicht durch Hinzufügen von mehr Syntax aufgebläht werden muss, wenn eine Schließung die Aufgabe ordentlich erledigt.
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)}}}})();
Ich mag diesen Ansatz, weil er die Anliegen gut voneinander trennt und die Dinge wirklich privat hält. Der einzige Nachteil ist die Notwendigkeit, "Selbst" (oder etwas Ähnliches) zu verwenden, um in privaten Inhalten auf "dieses" zu verweisen.
Ich habe eine sehr einfache Lösung gefunden, benutze einfach Object.freeze()
. Das Problem ist natürlich, dass Sie dem Objekt später nichts hinzufügen können.
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
Siehe diese Antwort für eine saubere und einfache "Klassen" -Lösung mit einer privaten und öffentlichen Schnittstelle und Unterstützung für die Komposition
Ja, und das ganz leicht. Dazu legen Sie Ihre privaten Variablen und Funktionen offen, indem Sie den Prototypobjektgraph im Konstruktor zurückgeben. Dies ist nichts Neues, aber nehmen Sie sich ein bisschen Foo, um die Eleganz zu verstehen. Auf diese Weise werden keine globalen Bereiche oder Schwachmaps verwendet. Es ist eine Form der Reflexion, die in die Sprache eingebaut ist. Abhängig davon, wie Sie dies nutzen; Man kann entweder eine Ausnahme erzwingen, die den Aufrufstapel unterbricht, oder die Ausnahme als undefined
begraben. Dies wird im Folgenden erläutert und Sie können mehr über diese Funktionen lesen hier
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
Ich benutze dieses Muster und es hat immer für mich funktioniert
class Test {
constructor(data) {
class Public {
constructor(prv) {
// public function (must be in constructor on order to access "prv" variable)
connectToDb(ip) {
prv._db(ip, prv._err);
}
}
// public function w/o access to "prv" variable
log() {
console.log("I'm logging");
}
}
// private variables
this._data = data;
this._err = function(ip) {
console.log("could not connect to "+ip);
}
}
// private function
_db(ip, err) {
if(!!ip) {
console.log("connected to "+ip+", sending data '"+this.data+"'");
return true;
}
else err(ip);
}
}
var test = new Test(10),
ip = "185.167.210.49";
test.connectToDb(ip); // true
test.log(); // I'm logging
test._err(ip); // undefined
test._db(ip, function() { console.log("You have got hacked!"); }); // undefined
Eigentlich ist es ist möglich.
1. Erstellen Sie zunächst die Klasse und geben Sie im Konstruktor die aufgerufene Funktion _public
Zurück.
2. Übergeben Sie in der aufgerufenen Funktion _public
Die Referenz this
(um auf alle privaten Methoden und Requisiten zuzugreifen) , und alle Argumente von constructor
(die in new Names()
übergeben werden)
3. Im Funktionsumfang _public
Gibt es auch die Klasse Names
mit dem Zugriff auf die Referenz this
(_this) der Klasse private Names
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);
}
Es ist möglich, private Methoden in Klassen zu verwenden, die WeakMap
verwenden.
Das WeakMap-Objekt ist eine Sammlung von Schlüssel/Wert-Paaren, bei denen die Schlüssel nur Objekte sind und die Werte beliebige Werte sein können.
Die Objektreferenzen in den Schlüsseln werden schwach gehalten, was bedeutet, dass sie ein Ziel der Garbage Collection (GC) sind, wenn es keine anderen Referenzen mehr auf das Objekt gibt.
Und dies ist ein Beispiel für das Erstellen einer Queue
-Datenstruktur mit einem privaten Member _items
, das ein Array enthält.
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];
}
}
Ein Beispiel für die Verwendung von:
const c = new Queue();
c.enqueue("one");
c.enqueue("two");
c.enqueue("three");
c.enqueue("four");
c.enqueue("five");
console.log(c);
Der private Member _items
wird ausgeblendet und kann nicht in den Eigenschaften oder Methoden eines Queue
-Objekts angezeigt werden:
Das private Mitglied _items
im Objekt Queue
kann jedoch folgendermaßen aufgerufen werden:
const anArray = _items.get(this);
Ein anderer Weg ähnlich den letzten beiden gepostet
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
Oh, so viele exotische Lösungen! Normalerweise kümmere ich mich nicht um die Privatsphäre, daher verwende ich "Pseudo Privacy", wie es hier gesagt ist. Wenn Sie sich jedoch darum kümmern (wenn es spezielle Anforderungen dafür gibt), verwende ich etwas wie in diesem Beispiel:
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
Eine weitere mögliche Implementierung der Funktion (Konstruktor) Job
:
function Job(name){
var impl = new jobImpl(name);
this.do = time => impl.do(time),
this.stop = time => impl.stop(time)
}
Hier ist die Variable myThing privat und ist Teil der Schließung:
class Person {
constructor() {
var myThing = "Hello World";
return {
thing: myThing,
sayThing: this.sayThing
}
}
sayThing() {
console.log(this.thing);
}
}
var person = new Person();
console.log(person);
Sie können dieses https://www.npmjs.com/package/private-members ausprobieren.
Dieses Paket speichert die Mitglieder nach Instanz.
const pvt = require('private-members');
const _ = pvt();
let Exemplo = (function () {
function Exemplo() {
_(this).msg = "Minha Mensagem";
}
_().mensagem = function() {
return _(this).msg;
}
Exemplo.prototype.showMsg = function () {
let msg = _(this).mensagem();
console.log(msg);
};
return Exemplo;
})();
module.exports = Exemplo;
Die meisten Antworten geben entweder an, dass es unmöglich ist, oder erfordern die Verwendung einer WeakMap oder eines Symbols. Hierbei handelt es sich um ES6-Funktionen, für die wahrscheinlich Polyfills erforderlich sind. Es gibt jedoch einen anderen Weg! Schau dir das an:
// 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
Ich nenne diese Methode Accessormuster. Die grundlegende Idee ist, dass wir innerhalb der Schließung ein Schließung, einen Schlüssel haben und ein privates Objekt (im Konstruktor) erstellen, auf das nur zugegriffen werden kann wenn Sie den Schlüssel haben.
Wenn Sie interessiert sind, können Sie mehr darüber in meinem Artikel nachlesen. Mit dieser Methode können Sie pro Objekteigenschaften erstellen, auf die außerhalb der Schließung nicht zugegriffen werden kann. Daher können Sie sie im Konstruktor oder Prototyp verwenden, aber nicht anderswo. Ich habe diese Methode nirgendwo verwendet, aber ich denke, sie ist wirklich mächtig.
Wie wir wissen, gibt es keine native Unterstützung für private Eigenschaften mit ES6-Klassen.
Unten ist genau das, was ich benutze (kann hilfreich sein). Im Grunde packe ich eine Klasse in der Fabrik.
function Animal(name) {
const privateData = 'NO experiments on animals have been done!';
class Animal {
constructor(_name) {
this.name = _name;
}
getName() {
return this.name
}
getDisclamer() {
return `${privateData} Including ${this.name}`
}
}
return new Animal(name)
}
Ich bin ein Anfänger und freue mich zu hören, ob dies ein schlechter Ansatz ist.
Beim Lesen der vorherigen Antwort dachte ich, dass dieses Beispiel die obigen Lösungen zusammenfassen kann
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;
}
})();
Ich habe ein Modul entwickelt, mit dem Sie die Zugriffsbeschränkung in der JavaScript-Klasse mit dem Namen Capsulable verwenden können. (Private & Protected Static)
Wenn Sie interessiert sind, schauen Sie sich mein Paket unten an . https://github.com/hmmhmmhm/capsulable
const Capsulable = require('capsulable')
const Field = Capsulable()
class A {
constructor(_field){
// Configure data fields.
Field(this, _field)
// The code below provides access to
// the data fields when creating
// functions within the class.
Field(this).private
Field(this).protected
Field(this).protectedStatic
}
}
module.exports = A