Créez une propriété qui est en lecture seule sur le monde extérieur, mais mes méthodes peuvent toujours être configurées

En JavaScript (ES5 +), j'essaie d'obtenir le scénario suivant:

  1. Un objet (dont il y aura plusieurs instances distinctes) chacune avec une propriété en lecture seule. .size qui peut être lue de l'extérieur via lecture directe de propriété, mais ne peut pas être définie de l'extérieur.
  2. La propriété .size doit être maintenue / mise à jour à partir de certaines méthodes qui sont sur le prototype (et devraient rester sur le prototype).
  3. Mon API est déjà définie par une spécification, donc je ne peux pas modifier cela (je travaille sur un polyfill pour un objet ES6 déjà défini).
  4. Je tente surtout d'empêcher les gens de se tirer eux-mêmes accidentellement sur le pied et ne doivent pas nécessairement avoir une lecture sans pareil à l'épreuve des balles (bien qu'il soit plus anti-balles, mieux c'est), alors je suis prêt à compromettre certains Sur l'accès de la porte latérale à la propriété tant que le réglage direct obj.size = 3; N'est pas autorisé.

Je suis conscient que je pourrais utiliser une variable privée déclarée dans le constructeur et configurer un getter pour le lire, mais je devais déplacer les méthodes qui doivent maintenir cette variable hors du prototype et les déclarer également dans le constructeur (alors Ils ont accès à la fermeture contenant la variable). Pour cette circonstance particulière, je préfère ne pas retirer mes méthodes du prototype, alors je cherche les autres options possibles.

Quelles autres idées pourrait-il y avoir (même s'il y a des compromis)?

OK, alors, pour une solution, vous avez besoin de deux parties:

  • Une propriété de size qui n'est pas assignable, c'est-à-dire avec des attributs d' writable:true ou no setter
  • Un moyen de modifier la valeur que reflète la size , qui n'est pas .size = … et qui est public afin que les méthodes prototypes puissent l'invoquer.

@plalx a déjà présenté la manière évidente avec une deuxième propriété " _size " _size qui est reflétée par un getter pour la size . C'est probablement la solution la plus simple et la plus simple:

 // declare Object.defineProperty(MyObj.prototype, "size", { get: function() { return this._size; } }); // assign instance._size = …; 

Une autre façon serait de rendre la propriété de la size non accessible à l'écriture, mais configurable, de sorte que vous devez utiliser «long-way» avec Object.defineProperty (bien que même trop peu pour une fonction helper) pour définir une valeur:

 function MyObj() { // Constructor // declare Object.defineProperty(this, "size", { writable: false, enumerable: true, configurable: true }); } // assign Object.defineProperty(instance, "size", {value:…}); 

Ces deux méthodes sont définitivement suffisantes pour éviter les "tir dans le pied" size = … affectations. Pour une approche plus sophistiquée, nous pourrions créer une méthode de setter public, spécifique à une instance (fermeture) qui ne peut être invoquée que par des méthodes prototypes de module-scope.

 (function() { // module IEFE // with privileged access to this helper function: var settable = false; function setSize(o, v) { settable = true; o.size = v; settable = false; } function MyObj() { // Constructor // declare var size; Object.defineProperty(this, "size", { enumerable: true, get: function() { return size; }, set: function(v) { if (!settable) throw new Error("You're not allowed."); size = v; } }); … } // assign setSize(instance, …); … }()); 

Cela est en effet sécurisé tant qu'il n'y a pas d'accès sécurisé à l'installation. Il existe également une approche similaire, populaire, peu courte est d'utiliser l'identité d'un objet comme un jeton d'accès, selon les lignes suivantes:

 // module IEFE with privileged access to this token: var token = {}; // in the declaration (similar to the setter above) this._setSize = function(key, v) { if (key !== token) throw new Error("You're not allowed."); size = v; }; // assign instance._setSize(token, …); 

Cependant, ce modèle n'est pas sécurisé car il est possible de voler le token en appliquant un code avec l'affectation à un objet personnalisé avec une méthode _setSize malveillante.

Honnêtement, je trouve qu'il y a trop de sacrifices à faire pour faire respecter une véritable confidentialité dans JS (sauf si vous définissez un module), alors je préfère compter uniquement sur les conventions de dénomination telles que this._myPrivateVariable .

Il s'agit d'un indicateur clair pour tout développeur qu'ils ne devraient pas accéder ou modifier ce membre directement et il ne faut pas sacrifier les avantages de l'utilisation de prototypes.

Si vous avez besoin d'avoir accès à votre membre de size comme propriété, vous n'aurez d'autre choix que de définir un getter sur le prototype.

 function MyObj() { this._size = 0; } MyObj.prototype = { constructor: MyObj, incrementSize: function () { this._size++; }, get size() { return this._size; } }; var o = new MyObj(); o.size; //0 o.size = 10; o.size; //0 o.incrementSize(); o.size; //1 

Une autre approche que j'ai vu est d'utiliser le modèle de module afin de créer une carte d'objet privée qui tiendra les variables privées des instances individuelles. Lors de l'instanciation, une clé privée en lecture seule est attribuée sur l'instance et cette clé est ensuite utilisée pour définir ou récupérer des valeurs à partir de l'objet privates .

 var MyObj = (function () { var privates = {}, key = 0; function initPrivateScopeFor(o) { Object.defineProperty(o, '_privateKey', { value: key++ }); privates[o._privateKey] = {}; } function MyObj() { initPrivateScopeFor(this); privates[this._privateKey].size = 0; } MyObj.prototype = { constructor: MyObj, incrementSize: function () { privates[this._privateKey].size++; }, get size() { return privates[this._privateKey].size; } }; return MyObj; })(); 

Comme vous l'avez peut-être remarqué, ce modèle est intéressant, mais l'implémentation ci-dessus est erronée car les variables privées ne recevront jamais les ordures collectées même s'il n'y a plus de référence à l'objet d'instance qui maintient la clé.

Cependant, avec ES6 WeakMap s, ce problème disparaît et il simplifie même la conception car nous pouvons utiliser l'instance d'objet comme clé au lieu d'un nombre tel que nous l'avons fait plus haut. Si l'instance obtient des ordures collectées, la carte faible n'empêchera pas la collecte des ordures de la valeur référencée par cet objet.