web-dev-qa-db-fra.com

Propriétés privées dans les classes JavaScript ES6

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"
362
d13

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
67
Alister Norris

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; }
}
228
MetalGodwin

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.

Scoped Variables

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

Carte Wapopée

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.)

Demi-réponse: symboles de portée

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)`.

Demi-réponse: traits de soulignement

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.

Conclusion

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.

159
twhb

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 Symbols .

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
111

La réponse est non". Mais vous pouvez créer un accès privé à des propriétés comme celle-ci:

(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 )

28
d13

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));
  }
}
26
loganfsmyth

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}`)
      }
    }
  }
}
21
user1076821

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.

12
Bergi

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: 

  • nous pouvons avoir des méthodes privées (_greet et _updateMessage agissent comme des méthodes privées tant que nous n'avons pas export les références) 
  • bien qu'elles ne soient pas sur le prototype, les méthodes mentionnées ci-dessus économiseront de la mémoire car les instances sont créées une fois, en dehors de la classe (par opposition à leur définition dans le constructeur). 
  • nous ne fuient pas de globals puisque nous sommes à l'intérieur d'un module 
  • nous pouvons également avoir des propriétés privées en utilisant l'objet _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

9
efidiles

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
8
Nikita Kurtin

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:

quelque chose.js

let _message = null;
const _greet = name => {
  console.log('Hello ' + name);
};

export default class Something {
  constructor(message) {
    _message = message;
  }

  say() {
    console.log(_message);
    _greet('Bob');
  }
};

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

Mise à jour (Important):

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
8
Johnny Oshika

Une approche différente du "privé"

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:

 auto-complete showing just public stuff

6
Lucio Paiva

WeakMap

  • pris en charge dans IE11 (les symboles ne sont pas)
  • hard-private (les accessoires utilisant des symboles sont soft-private en raison de Object.getOwnPropertySymbols)
  • peut sembler très propre (contrairement aux fermetures qui nécessitent tous les accessoires et méthodes du constructeur)

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.

6
kevlened

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.

4
NanoWizard

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.

4
Robin F.

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);
   }
};

4
JSInitiate

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.

4
Francisco Neto

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();
3
MarkM
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"
3
Ilya Zarembsky

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.

3
Michael Franzl

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é.

3
Paul Whipp

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
2
Nikola Andreev

Voir cette réponse pour une solution 'classe' propre et simple avec une interface privée et publique et un support pour la composition

2
kofifus

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

2
1-14x0r

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

2
Yami Teru

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);
}
2
Paweł

Il est possible d'avoir des méthodes privées dans les classes en utilisant WeakMap.

Selon les documents Web MDN :

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:

 enter image description here

Cependant, le membre privé _items dans l'objet Queue est accessible de la manière suivante:

const anArray = _items.get(this);
1
StepUp

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
1
Jayesbe

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)
}
1
Sergey

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);
1
Naga Chaitanya Konada

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;
1
João Henrique

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.

1
guitarino

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.

0
Alexander Sharikov

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;
    }
})();
0
asdru

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
0
hmmhmmhm