Est-il possible de créer des propriétés privées dans les classes ES6?
Voici un exemple . Comment puis-je empêcher l'accès à instance.property
?
class Something {
constructor(){
this.property = "test";
}
}
var instance = new Something();
console.log(instance.property); //=> "test"
Les champs privés sont mis en œuvre dans le norme ECMA . Vous pouvez commencer à les utiliser dès aujourd'hui avec babel 7 et le préréglage de l'étape 3. Voir exemple de babel REPL .
class Something {
#property;
constructor(){
this.#property = "test";
}
}
const instance = new Something();
console.log(instance.property); //=> undefined
Réponse courte, non, il n'y a pas de support natif pour les propriétés privées avec les classes ES6.
Mais vous pouvez imiter ce comportement en ne liant pas les nouvelles propriétés à l'objet, mais en les conservant dans un constructeur de classe, et en utilisant des getters et des setters pour atteindre les propriétés masquées. Notez que les getters et les setters sont redéfinis à chaque nouvelle instance de la classe.
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; }
}
Pour développer la réponse de @ loganfsmyth:
Les seules données véritablement privées en JavaScript sont toujours des variables périmées. Vous ne pouvez pas avoir de propriétés privées au sens de propriétés accessibles en interne de la même manière que les propriétés publiques, mais vous pouvez utiliser des variables étendues pour stocker des données privées.
L’approche consiste ici à utiliser la portée de la fonction constructeur, qui est privée, pour stocker des données privées. Pour que les méthodes aient accès à ces données privées, elles doivent également être créées dans le constructeur, ce qui signifie que vous les recréez à chaque instance. C’est une pénalité de performance et de mémoire, mais certains pensent que la pénalité est acceptable. La pénalité peut être évitée pour les méthodes n'ayant pas besoin d'accéder à des données privées en les ajoutant au prototype comme d'habitude.
Exemple:
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
Un WeakMap peut être utilisé pour éviter les pénalités de performance et de mémoire de l'approche précédente. Les WeakMaps associent des données à des objets (ici, des instances) de manière à ce qu’elles ne puissent être accessibles qu’avec ce WeakMap. Nous utilisons donc la méthode des variables scoped pour créer un WeakMap privé, puis nous utilisons ce WeakMap pour récupérer des données privées associées à this
. Cette méthode est plus rapide que la méthode des variables scoped, car toutes vos instances peuvent partager un seul WeakMap. Vous n'avez donc pas besoin de recréer des méthodes pour leur permettre d'accéder à leurs propres WeakMaps.
Exemple:
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
Cet exemple utilise un objet pour utiliser un WeakMap pour plusieurs propriétés privées; vous pouvez également utiliser plusieurs WeakMaps et les utiliser comme age.set(this, 20)
ou écrire un petit wrapper et l'utiliser d'une autre manière, comme privateProps.set(this, 'age', 0)
.
La confidentialité de cette approche pourrait théoriquement être violée en altérant l'objet global WeakMap
. Cela dit, tout le JavaScript peut être cassé par des globals mutilés. Notre code repose déjà sur l'hypothèse que cela ne se produit pas.
(Cette méthode peut également être utilisée avec Map
, mais WeakMap
est préférable, car Map
créera des fuites de mémoire à moins que vous ne soyez très prudent et que, pour ce faire, les deux ne sont pas différents.)
Un symbole est un type de valeur primitive pouvant servir de nom de propriété. Vous pouvez utiliser la méthode de la variable Scoped pour créer un symbole privé, puis stocker des données privées à this[mySymbol]
.
La confidentialité de cette méthode peut être violée à l'aide de Object.getOwnPropertySymbols
, mais est un peu délicate à faire.
Exemple:
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)`.
L'ancien par défaut, utilisez simplement une propriété publique avec un préfixe de soulignement. Bien qu’il ne s’agisse en aucun cas d’une propriété privée, cette convention est suffisamment répandue pour que les lecteurs puissent la traiter comme une propriété privée, ce qui est souvent utile. En échange de cette erreur, nous obtenons une approche plus facile à lire, à saisir et plus rapidement.
Exemple:
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.
Depuis ES2017, il n’existait toujours pas de moyen idéal de créer des propriétés privées. Diverses approches ont des avantages et des inconvénients. Les variables scoped sont vraiment privées; Les WeakMaps scoped sont très privés et plus pratiques que les variables scoped; Les symboles de portée sont raisonnablement privés et raisonnablement pratiques; Les soulignés sont souvent assez privés et très pratiques.
Mise à jour: Une proposition avec une syntaxe plus agréable est sur le point de l’être. Les contributions sont les bienvenues.
Oui, il existe - pour l'accès limité dans les objets - ES6 introduit Symbol
s .
Les symboles sont uniques, vous ne pouvez pas y accéder de l'extérieur, sauf en cas de réflexion (comme dans le cas de parties privées en Java/C #), mais toute personne ayant accès à un symbole à l'intérieur peut l'utiliser pour accéder à une clé:
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
La réponse est non". Mais vous pouvez créer un accès privé à des propriétés comme celle-ci:
export
.(La suggestion selon laquelle les symboles pourraient être utilisés pour garantir la confidentialité était vraie dans une version antérieure de la spécification ES6, mais ce n'est plus le cas: https://mail.mozilla.org/pipermail/es-discuss/2014-January/ 035604.html et https://stackoverflow.com/a/22280202/1282216 Pour une discussion plus longue sur les symboles et la confidentialité, voir: https://curiosity-driven.org/private-properties- en javascript )
Le seul moyen d'obtenir une véritable confidentialité dans JS consiste à définir la portée. Par conséquent, il est impossible d'avoir une propriété membre de this
qui sera accessible uniquement à l'intérieur du composant. Le meilleur moyen de stocker des données réellement privées dans ES6 consiste à utiliser une carte 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));
}
}
De toute évidence, il s’agit probablement d’un processus lent et certainement moche, mais cela garantit la confidentialité.
N'oubliez pas que MÊME CECI n'est pas parfait, car Javascript est très dynamique. Quelqu'un pourrait encore faire
var oldSet = WeakMap.prototype.set;
WeakMap.prototype.set = function(key, value){
// Store 'this', 'key', and 'value'
return oldSet.call(this, key, value);
};
pour capturer les valeurs lorsqu’elles sont stockées, si vous voulez faire très attention, vous devez capturer une référence locale à .set
et .get
à utiliser explicitement au lieu de vous fier au prototype qui peut être remplacé.
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));
}
}
Pour des références futures à d'autres observateurs, j'entends maintenant que la recommandation est d'utiliser WeakMaps pour conserver des données privées.
Voici un exemple de travail plus clair:
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}`)
}
}
}
}
Dépend de à qui vous demandez :-)
Aucun modificateur de propriété private
n'est inclus dans la proposition Classes minimales minimales qui semble l'avoir intégré dans le groupe - version actuelle .
Cependant, il pourrait y avoir support for noms privés , ce qui autorise les propriétés privées - et ils pourrait probablement aussi être utilisé dans les définitions de classe.
Complétez @ d13 et les commentaires de @ johnny-oshika et @DanyalAytekin:
Je suppose que dans l'exemple fourni par @ johnny-oshika, nous pourrions utiliser des fonctions normales à la place des fonctions de flèche, puis .bind
avec l'objet en cours plus un objet _privates
en tant que paramètre curry:
quelque chose.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
Avantages auxquels je peux penser:
_greet
et _updateMessage
agissent comme des méthodes privées tant que nous n'avons pas export
les références) _privates
liéQuelques inconvénients auxquels je peux penser:
Un extrait en cours d'exécution peut être trouvé ici: http://www.webpackbin.com/NJgI5J8lZ
Yes - vous pouvez créer une propriété encapsulée, mais cela n'a pas été fait avec des modificateurs d'accès (public | privé), du moins pas avec ES6.
Voici un exemple simple sur la manière de procéder avec ES6:
1 Créer une classe avec class Word
2 À l'intérieur de son constructeur, déclarez la variable liée au bloc à l'aide de let OR const mots réservés -> car ils sont bloc-portée, l'accès ne peut pas être accessible de l'extérieur (encapsulés)
3 Pour permettre un certain contrôle d'accès (setters | getters) à ces variables, vous pouvez déclarer une méthode d'instance dans son constructeur à l'aide de la syntaxe suivante: this.methodName=function(){}
"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;
}
}
}
Voyons maintenant:
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
Utiliser les modules ES6 (initialement proposés par @ d13) me convient bien. Cela n'imite pas parfaitement les propriétés privées, mais au moins, vous pouvez être sûr que les propriétés qui devraient être privées ne sortiront pas de votre classe. Voici un exemple:
let _message = null;
const _greet = name => {
console.log('Hello ' + name);
};
export default class Something {
constructor(message) {
_message = message;
}
say() {
console.log(_message);
_greet('Bob');
}
};
Le code consommateur peut alors ressembler à ceci:
import Something from './something.js';
const something = new Something('Sunny day!');
something.say();
something._message; // undefined
something._greet(); // exception
Comme @DanyalAytekin l'a souligné dans les commentaires, ces propriétés privées sont statiques, donc globales. Ils fonctionneront bien quand on travaillera avec Singletons, mais il faudra prendre soin des objets transitoires. Prolonger l'exemple ci-dessus:
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
Au lieu de lutter contre le fait que la visibilité privée est actuellement indisponible dans ES6, j'ai décidé d'adopter une approche plus pratique qui fonctionne parfaitement si votre IDE prend en charge JSDoc (par exemple, Webstorm). L'idée est d'utiliser la balise @private
. En ce qui concerne le développement, le IDE vous empêchera d'accéder à un membre privé en dehors de sa classe. Cela fonctionne assez bien pour moi et cela a été très utile pour masquer les méthodes internes, donc la fonctionnalité de saisie semi-automatique me montre exactement ce que la classe voulait vraiment exposer. Voici un exemple:
WeakMap
Object.getOwnPropertySymbols
)Tout d’abord, définissez une fonction pour envelopper WeakMap:
function Private() {
const map = new WeakMap();
return obj => {
let props = map.get(obj);
if (!props) {
props = {};
map.set(obj, props);
}
return props;
};
}
Ensuite, construisez une référence en dehors de votre classe:
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
}
}
Remarque: class n'est pas pris en charge par IE11, mais il semble plus clair dans l'exemple.
Je pense La réponse de Benjamin est probablement la meilleure solution dans la plupart des cas, jusqu'à ce que le langage prenne en charge de manière native des variables explicitement privées.
Toutefois, si, pour une raison quelconque, vous devez empêcher l'accès avec Object.getOwnPropertySymbols()
, une méthode que j'ai envisagé d'utiliser consiste à attacher une propriété unique, non configurable, non énumérable et non enregistrable pouvant être utilisée comme identificateur de propriété à chaque objet en construction (par exemple, une Symbol
unique, si vous ne possédez pas déjà une autre propriété unique telle qu'une id
). Ensuite, conservez une carte des variables "privées" de chaque objet en utilisant cet identifiant.
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"
L'avantage potentiel de cette approche par rapport à l'utilisation de WeakMap
est un temps d'accès plus rapide / si les performances deviennent un sujet de préoccupation.
Personnellement, j’aime bien la proposition de l’opérateur bind::
et la combine ensuite avec la solution @ d13 mentionnée, mais pour l’instant, conservez la réponse de @ d13 dans laquelle vous utilisez le mot clé export
pour votre classe et placez les fonctions privées dans le module.
il existe une autre solution difficile qui n’est pas mentionnée ici et qui suit est une approche plus fonctionnelle et lui permettrait de disposer de tous les accessoires/méthodes privés de la classe.
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
des commentaires à ce sujet seraient appréciés.
Je pense qu'il est possible d'obtenir le meilleur des deux mondes en utilisant des fermetures à l'intérieur de constructeurs. Il y a deux variations:
Toutes les données membres sont privées
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);
}
};
Certains membres sont privés
NOTE: Ceci est certes moche. Si vous connaissez une meilleure solution, veuillez éditer cette réponse.
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);
}
};
En fait, il est possible d’utiliser des symboles et des proxies. Vous utilisez les symboles dans la portée de la classe et définissez deux interruptions dans un proxy: une pour le prototype de classe, de sorte que Reflect.ownKeys (instance) ou Object.getOwnPropertySymbols ne divulgue pas vos symboles, l'autre pour le constructeur lui-même. Ainsi, lorsque new ClassName(attrs)
est appelé, l'instance renvoyée est interceptée et les symboles de propriétés propres sont bloqués . Voici le 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()
fonctionne comme suit: Object.getOwnPropertyNames(myObj).concat(Object.getOwnPropertySymbols(myObj))
c'est pourquoi nous avons besoin d'un piège pour ces objets.
Je suis tombé sur cet article en cherchant la meilleure pratique pour "données privées pour les classes". Il a été mentionné que quelques-uns des modèles auraient des problèmes de performance.
J'ai mis en place quelques tests jsperf basés sur les 4 modèles principaux du livre en ligne "Exploring ES6":
http://exploringjs.com/es6/ch_classes.html#sec_private-data-for-classes
Les tests peuvent être trouvés ici:
https://jsperf.com/private-data-for-classes
Dans Chrome 63.0.3239/Mac OS X 10.11.6, les modèles les plus performants étaient "Données privées via des environnements de constructeur" et "Données privées via une convention de dénomination". Pour moi, Safari a bien fonctionné pour WeakMap mais Chrome n'a pas si bien réussi.
Je ne connais pas l’impact de la mémoire, mais le modèle d’environnements constructeur que certains avaient prédit comme un problème de performances était très performant.
Les 4 modèles de base sont:
Données privées via des environnements constructeurs
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();
Données privées via des environnements de constructeurs 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();
Données privées via une convention de nommage
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();
Données privées via 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();
Données privées via des symboles
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"
Même TypeScript ne peut pas le faire. De leur documentation :
Lorsqu'un membre est marqué comme privé, il est impossible d'y accéder de l'extérieur de la classe qu'il contient. Par exemple:
class Animal { private name: string; constructor(theName: string) { this.name = theName; } } new Animal("Cat").name; // Error: 'name' is private;
Mais transpilé sur leur terrain de jeu cela donne:
var Animal = (function () {
function Animal(theName) {
this.name = theName;
}
return Animal;
}());
console.log(new Animal("Cat").name);
Donc, leur mot clé "privé" est inefficace.
J'arrive très tard à cette soirée, mais je pose la question à l'OP lors d'une recherche. Donc, ____Oui, vous pouvez avoir des propriétés privées en enveloppant la déclaration de classe dans une clôture
Il y a un exemple de la façon dont j'ai des méthodes privées dans ce codepen . Dans l'extrait de code ci-dessous, la classe Subscribable a deux fonctions 'privées' process
et processCallbacks
. Toutes les propriétés peuvent être ajoutées de cette manière et elles restent privées grâce à l'utilisation de la fermeture. La confidentialité de l'OMI est un besoin rare si les problèmes sont bien séparés et que Javascript n'a pas besoin d'être compliqué par l'ajout de syntaxe supplémentaire lorsqu'une fermeture fait parfaitement l'affaire.
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)}}}})();
J'aime cette approche parce qu'elle sépare bien les préoccupations et garde les choses vraiment privées. Le seul inconvénient est la nécessité d'utiliser "self" (ou quelque chose de similaire) pour faire référence à "ceci" dans le contenu privé.
J'ai trouvé une solution très simple, utilisez simplement Object.freeze()
. Bien sûr, le problème est que vous ne pouvez rien ajouter à l'objet plus tard.
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
Voir cette réponse pour une solution 'classe' propre et simple avec une interface privée et publique et un support pour la composition
Oui totalement, et assez facilement aussi. Cela se fait en exposant vos variables et fonctions privées en renvoyant le graphe d'objet prototype dans le constructeur. Ce n’est pas nouveau, mais prenez un peu de js foo pour comprendre son élégance. Cette méthode n'utilise pas de portée globale ni de mappe faible. C'est une forme de réflexion intégrée au langage. En fonction de la manière dont vous exploitez cela; on peut soit forcer une exception qui interrompt la pile d'appels, soit l'enterrer en tant que undefined
. Ceci est démontré ci-dessous, et vous pouvez en savoir plus sur ces fonctionnalités ici
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
J'utilise ce modèle et cela a toujours fonctionné pour moi
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
En fait c'est est possible.
1. Commencez par créer la classe et dans le constructeur, renvoyez la fonction _public
appelée.
2. Dans la fonction _public
appelée, passez la référence this
(pour obtenir l'accès à toutes les méthodes et accessoires privés), ainsi que tous les arguments de constructor
(qui seront passés dans new Names()
)
3. Dans la portée de la fonction _public
, il y a aussi la classe Names
avec l'accès à la référence this
(_this) de la classe privée 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);
}
Il est possible d'avoir des méthodes privées dans les classes en utilisant WeakMap
.
L'objet WeakMap est un ensemble de paires clé/valeur dans lesquelles les clés sont uniquement des objets et les valeurs peuvent être des valeurs arbitraires.
Les références d'objet dans les clés sont maintenues faiblement, ce qui signifie qu'elles sont une cible de la récupération de place s'il n'y a plus d'autre référence à l'objet.
Et ceci est un exemple de création de la structure de données Queue
avec un membre privé _items
qui contient un tableau.
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];
}
}
Un exemple d'utilisation:
const c = new Queue();
c.enqueue("one");
c.enqueue("two");
c.enqueue("three");
c.enqueue("four");
c.enqueue("five");
console.log(c);
Le membre privé _items
est masqué et ne peut pas être vu dans les propriétés ou les méthodes d'un objet Queue
:
Cependant, le membre privé _items
dans l'objet Queue
est accessible de la manière suivante:
const anArray = _items.get(this);
Une autre façon semblable aux deux derniers postés
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, tellement de solutions exotiques! Habituellement, je ne me soucie pas de la vie privée, alors j’utilise "pseudo-vie privée" comme il est dit ici . Mais si attention (s'il y a des exigences spéciales pour cela), j'utilise quelque chose comme dans cet exemple:
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
Une autre implémentation possible de la fonction (constructeur) Job
:
function Job(name){
var impl = new jobImpl(name);
this.do = time => impl.do(time),
this.stop = time => impl.stop(time)
}
Ici, la variable myThing est privée et fait partie de la fermeture:
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);
Vous pouvez essayer ceci https://www.npmjs.com/package/private-members
Ce paquet sauvera les membres par instance.
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;
La plupart des réponses disent soit que c'est impossible, soit vous demandent d'utiliser un WeakMap ou un symbole, qui sont des fonctionnalités ES6 qui nécessiteraient probablement des remplissages multiples. Il y a cependant une autre façon! Découvrez ceci:
// 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
J'appelle cette méthode accessor pattern. L'idée essentielle est que nous avons un fermeture, une clé à l'intérieur de la fermeture et nous créons un objet privé (dans le constructeur) accessible uniquement si vous avez la clé.
Si vous êtes intéressé, vous pouvez en savoir plus à ce sujet dans mon article . En utilisant cette méthode, vous pouvez créer des propriétés par objet inaccessibles en dehors de la fermeture. Par conséquent, vous pouvez les utiliser en constructeur ou en prototype, mais pas ailleurs. Je n'ai jamais vu cette méthode utilisée nulle part, mais je pense que c'est vraiment puissant.
Comme nous le savons, il n’existe pas de support natif pour les propriétés privées avec les classes ES6.
Vous trouverez ci-dessous ce que j'utilise (cela pourrait être utile). Fondamentalement, j'emballe une classe à l'intérieur de l'usine.
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)
}
Je suis un débutant si heureux d'entendre si c'est une mauvaise approche.
En lisant la réponse précédente, je pensais que cet exemple pouvait résumer les solutions ci-dessus.
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;
}
})();
J'ai développé un module qui vous aide à utiliser la restriction d'accès de la classe JavaScript appelée. Capsulable. (Statique privée et protégée)
Si vous êtes intéressé, consultez mon paquet ci-dessous . 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