Existe-t-il un moyen de rendre les variables «privées» (celles définies dans le constructeur), disponibles aux méthodes définies par prototype?
TestClass = function(){
var privateField = "hello";
this.nonProtoHello = function(){alert(privateField)};
};
TestClass.prototype.prototypeHello = function(){alert(privateField)};
Cela marche:
t.nonProtoHello()
Mais ce n'est pas le cas:
t.prototypeHello()
J'ai l'habitude de définir mes méthodes à l'intérieur du constructeur, mais je m'éloigne de cela pour plusieurs raisons.
javascript
private-members
Morgancodes
la source
la source
Réponses:
Non, il n'y a aucun moyen de le faire. Ce serait essentiellement une approche inverse.
Les méthodes définies dans le constructeur ont accès aux variables privées car toutes les fonctions ont accès à la portée dans laquelle elles ont été définies.
Les méthodes définies sur un prototype ne sont pas définies dans la portée du constructeur et n'auront pas accès aux variables locales du constructeur.
Vous pouvez toujours avoir des variables privées, mais si vous voulez que les méthodes définies sur le prototype y aient accès, vous devez définir des getters et des setters sur l'
this
objet, auxquels les méthodes du prototype (ainsi que tout le reste) auront accès. Par exemple:function Person(name, secret) { // public this.name = name; // private var secret = secret; // public methods have access to private members this.setSecret = function(s) { secret = s; } this.getSecret = function() { return secret; } } // Must use getters/setters Person.prototype.spillSecret = function() { alert(this.getSecret()); };
la source
secret
une propriététhis
. JavaScript ne prend tout simplement pas en charge les variables privées avec les prototypes, car les prototypes sont liés au contexte du site d'appel, pas au contexte du «site de création».person.getSecret()
alors?Mise à jour: avec ES6, il existe un meilleur moyen:
Pour faire court, vous pouvez utiliser le nouveau
Symbol
pour créer des champs privés.Voici une excellente description: https://curiosity-driven.org/private-properties-in-javascript
Exemple:
var Person = (function() { // Only Person can access nameSymbol var nameSymbol = Symbol('name'); function Person(name) { this[nameSymbol] = name; } Person.prototype.getName = function() { return this[nameSymbol]; }; return Person; }());
Pour tous les navigateurs modernes avec ES5:
Vous pouvez utiliser uniquement des fermetures
La façon la plus simple de construire des objets est d'éviter complètement l'héritage prototypique. Définissez simplement les variables privées et les fonctions publiques dans la fermeture, et toutes les méthodes publiques auront un accès privé aux variables.
Ou vous pouvez utiliser uniquement des prototypes
En JavaScript, l'héritage prototypique est avant tout une optimisation . Il permet à plusieurs instances de partager des méthodes prototypes, plutôt que chaque instance ayant ses propres méthodes.
L'inconvénient est que
this
c'est la seule chose qui diffère à chaque fois qu'une fonction prototypique est appelée.Par conséquent, tous les champs privés doivent être accessibles via
this
, ce qui signifie qu'ils seront publics. Nous nous en tenons donc simplement aux conventions de dénomination des_private
champs.Ne vous embêtez pas à mélanger des fermetures avec des prototypes
Je pense que vous ne devriez pas mélanger des variables de fermeture avec des méthodes prototypes. Vous devez utiliser l'un ou l'autre.
Lorsque vous utilisez une fermeture pour accéder à une variable privée, les méthodes de prototype ne peuvent pas accéder à la variable. Donc, vous devez exposer la fermeture
this
, ce qui signifie que vous l'exposez publiquement d'une manière ou d'une autre. Il y a très peu à gagner avec cette approche.Lequel dois-je choisir?
Pour les objets vraiment simples, utilisez simplement un objet simple avec des fermetures.
Si vous avez besoin d'un héritage prototypique - pour l'héritage, les performances, etc. - respectez la convention de dénomination "_private" et ne vous souciez pas des fermetures.
Je ne comprends pas pourquoi les développeurs JS s'efforcent de rendre les champs vraiment privés.
la source
_private
convention de dénomination reste la meilleure solution si vous souhaitez tirer parti de l'héritage prototypique.Symbol
, qui est un excellent moyen de créer des champs privés. Voici une excellente explication: curiosity-driven.org/private-properties-in-javascriptSymbol
dans une fermeture qui englobe toute votre classe. De cette façon, toutes les méthodes de prototype peuvent utiliser le symbole, mais il n'est jamais exposé en dehors de la classe.Object.getOwnPropertySymbols
. Ce n'est donc que la confidentialité par l'obscurité.toString
. Ce n'est pas différent de Java ou C # ... les membres privés sont toujours accessibles via la réflexion, mais sont généralement fortement masqués. Ce qui renforce mon dernier point: "Je ne comprends pas pourquoi les développeurs JS essaient si fort de rendre les champs vraiment privés."Quand j'ai lu ceci, cela ressemblait à un défi difficile, alors j'ai décidé de trouver un moyen. Ce que j'ai trouvé, c'est CRAAAAZY mais ça marche totalement.
Tout d'abord, j'ai essayé de définir la classe dans une fonction immédiate afin que vous ayez accès à certaines des propriétés privées de cette fonction. Cela fonctionne et vous permet d'obtenir des données privées, cependant, si vous essayez de définir les données privées, vous constaterez bientôt que tous les objets partageront la même valeur.
var SharedPrivateClass = (function() { // use immediate function // our private data var private = "Default"; // create the constructor function SharedPrivateClass() {} // add to the prototype SharedPrivateClass.prototype.getPrivate = function() { // It has access to private vars from the immediate function! return private; }; SharedPrivateClass.prototype.setPrivate = function(value) { private = value; }; return SharedPrivateClass; })(); var a = new SharedPrivateClass(); console.log("a:", a.getPrivate()); // "a: Default" var b = new SharedPrivateClass(); console.log("b:", b.getPrivate()); // "b: Default" a.setPrivate("foo"); // a Sets private to "foo" console.log("a:", a.getPrivate()); // "a: foo" console.log("b:", b.getPrivate()); // oh no, b.getPrivate() is "foo"! console.log(a.hasOwnProperty("getPrivate")); // false. belongs to the prototype console.log(a.private); // undefined // getPrivate() is only created once and instanceof still works console.log(a.getPrivate === b.getPrivate); console.log(a instanceof SharedPrivateClass); console.log(b instanceof SharedPrivateClass);
Il y a de nombreux cas où cela serait adéquat, comme si vous vouliez avoir des valeurs constantes telles que les noms d'événements partagés entre les instances. Mais essentiellement, ils agissent comme des variables statiques privées.
Si vous avez absolument besoin d'accéder à des variables dans un espace de noms privé à partir de vos méthodes définies sur le prototype, vous pouvez essayer ce modèle.
var PrivateNamespaceClass = (function() { // immediate function var instance = 0, // counts the number of instances defaultName = "Default Name", p = []; // an array of private objects // create the constructor function PrivateNamespaceClass() { // Increment the instance count and save it to the instance. // This will become your key to your private space. this.i = instance++; // Create a new object in the private space. p[this.i] = {}; // Define properties or methods in the private space. p[this.i].name = defaultName; console.log("New instance " + this.i); } PrivateNamespaceClass.prototype.getPrivateName = function() { // It has access to the private space and it's children! return p[this.i].name; }; PrivateNamespaceClass.prototype.setPrivateName = function(value) { // Because you use the instance number assigned to the object (this.i) // as a key, the values set will not change in other instances. p[this.i].name = value; return "Set " + p[this.i].name; }; return PrivateNamespaceClass; })(); var a = new PrivateNamespaceClass(); console.log(a.getPrivateName()); // Default Name var b = new PrivateNamespaceClass(); console.log(b.getPrivateName()); // Default Name console.log(a.setPrivateName("A")); console.log(b.setPrivateName("B")); console.log(a.getPrivateName()); // A console.log(b.getPrivateName()); // B // private objects are not accessible outside the PrivateNamespaceClass function console.log(a.p); // the prototype functions are not re-created for each instance // and instanceof still works console.log(a.getPrivateName === b.getPrivateName); console.log(a instanceof PrivateNamespaceClass); console.log(b instanceof PrivateNamespaceClass);
J'adorerais avoir des commentaires de toute personne qui voit une erreur dans cette façon de faire.
la source
i
a été ajouté à toutes les instances. Ce n'est donc pas totalement «transparent» eti
pourrait encore être falsifié.voir la page de Doug Crockford à ce sujet . Vous devez le faire indirectement avec quelque chose qui peut accéder à la portée de la variable privée.
un autre exemple:
Incrementer = function(init) { var counter = init || 0; // "counter" is a private variable this._increment = function() { return counter++; } this._set = function(x) { counter = x; } } Incrementer.prototype.increment = function() { return this._increment(); } Incrementer.prototype.set = function(x) { return this._set(x); }
cas d'utilisation:
js>i = new Incrementer(100); [object Object] js>i.increment() 100 js>i.increment() 101 js>i.increment() 102 js>i.increment() 103 js>i.set(-44) js>i.increment() -44 js>i.increment() -43 js>i.increment() -42
la source
_set
viaset
? Pourquoi ne pas simplement le nommerset
pour commencer?Je suggère que ce serait probablement une bonne idée de décrire "avoir une affectation de prototype dans un constructeur" comme un anti-pattern Javascript. Pensez-y. C'est bien trop risqué.
Ce que vous faites là-bas lors de la création du deuxième objet (c.-à-d. B) redéfinit cette fonction prototype pour tous les objets qui utilisent ce prototype. Cela réinitialisera effectivement la valeur de l'objet a dans votre exemple. Cela fonctionnera si vous voulez une variable partagée et si vous créez toutes les instances d'objet à l'avance, mais cela semble trop risqué.
J'ai trouvé un bogue dans certains Javascript sur lesquels je travaillais récemment et qui était dû à cet anti-pattern exact. Il essayait de définir un gestionnaire de glisser-déposer sur l'objet particulier en cours de création, mais le faisait à la place pour toutes les instances. Pas bon.
La solution de Doug Crockford est la meilleure.
la source
@Kai
Cela ne fonctionnera pas. Si tu fais
var t2 = new TestClass();
alors
t2.prototypeHello
accédera à la section privée de t.@AnglesCrimes
L'exemple de code fonctionne correctement, mais il crée en fait un membre privé «statique» partagé par toutes les instances. Ce n'est peut-être pas la solution recherchée par les morgancodes.
Jusqu'à présent, je n'ai pas trouvé de moyen simple et propre de le faire sans introduire un hachage privé et des fonctions de nettoyage supplémentaires. Une fonction de membre privé peut être simulée dans une certaine mesure:
(function() { function Foo() { ... } Foo.prototype.bar = function() { privateFoo.call(this, blah); }; function privateFoo(blah) { // scoped to the instance by passing this to call } window.Foo = Foo; }());
la source
privateFoo
est complètement privé et donc invisible lors de l'obtention d'un fichiernew Foo()
. Seulebar()
est une méthode publique ici, qui a accès àprivateFoo
. Vous pouvez utiliser le même mécanisme pour les variables simples et les objets, mais vous devez toujours garder à l'esprit que ceux-ciprivates
sont en fait statiques et seront partagés par tous les objets que vous créez.Oui c'est possible. Le modèle de conception PPF résout simplement cela.
PPF est l'acronyme de Private Prototype Functions. Le PPF de base résout ces problèmes:
Pour le premier, juste:
C'est si simple. Par exemple:
// Helper class to store private data. function Data() {}; // Object constructor function Point(x, y) { // container for private vars: all private vars go here // we want x, y be changeable via methods only var data = new Data; data.x = x; data.y = y; ... } // Prototype functions now have access to private instance data Point.prototype.getX = function(data) { return data.x; } Point.prototype.getY = function(data) { return data.y; }
...
Lire l'histoire complète ici:
Modèle de conception PPF
la source
Vous pouvez réellement y parvenir en utilisant la vérification des accesseurs :
(function(key, global) { // Creates a private data accessor function. function _(pData) { return function(aKey) { return aKey === key && pData; }; } // Private data accessor verifier. Verifies by making sure that the string // version of the function looks normal and that the toString function hasn't // been modified. NOTE: Verification can be duped if the rogue code replaces // Function.prototype.toString before this closure executes. function $(me) { if(me._ + '' == _asString && me._.toString === _toString) { return me._(key); } } var _asString = _({}) + '', _toString = _.toString; // Creates a Person class. var PersonPrototype = (global.Person = function(firstName, lastName) { this._ = _({ firstName : firstName, lastName : lastName }); }).prototype; PersonPrototype.getName = function() { var pData = $(this); return pData.firstName + ' ' + pData.lastName; }; PersonPrototype.setFirstName = function(firstName) { var pData = $(this); pData.firstName = firstName; return this; }; PersonPrototype.setLastName = function(lastName) { var pData = $(this); pData.lastName = lastName; return this; }; })({}, this); var chris = new Person('Chris', 'West'); alert(chris.setFirstName('Christopher').setLastName('Webber').getName());
Cet exemple provient de mon article sur les fonctions prototypiques et les données privées et y est expliqué plus en détail.
la source
JavaScript actuel, je suis assez certain qu'il ya une et une seule façon d'avoir l' état privé , accessible à partir de prototypes fonctions, sans ajouter quoi que ce soit du public à
this
. La réponse est d'utiliser le modèle de «carte faible».Pour résumer: la
Person
classe a une seule carte faible, où les clés sont les instances de Person, et les valeurs sont des objets simples qui sont utilisés pour le stockage privé.Voici un exemple entièrement fonctionnel: (jouez sur http://jsfiddle.net/ScottRippey/BLNVr/ )
var Person = (function() { var _ = weakMap(); // Now, _(this) returns an object, used for private storage. var Person = function(first, last) { // Assign private storage: _(this).firstName = first; _(this).lastName = last; } Person.prototype = { fullName: function() { // Retrieve private storage: return _(this).firstName + _(this).lastName; }, firstName: function() { return _(this).firstName; }, destroy: function() { // Free up the private storage: _(this, true); } }; return Person; })(); function weakMap() { var instances=[], values=[]; return function(instance, destroy) { var index = instances.indexOf(instance); if (destroy) { // Delete the private state: instances.splice(index, 1); return values.splice(index, 1)[0]; } else if (index === -1) { // Create the private state: instances.push(instance); values.push({}); return values[values.length - 1]; } else { // Return the private state: return values[index]; } }; }
Comme je l'ai dit, c'est vraiment le seul moyen de réaliser les 3 parties.
Il y a cependant deux mises en garde. Premièrement, cela coûte des performances - chaque fois que vous accédez aux données privées, c'est une
O(n)
opération, oùn
est le nombre d'instances. Vous ne voudrez donc pas faire cela si vous avez un grand nombre d'instances. Deuxièmement, lorsque vous avez terminé avec une instance, vous devez appelerdestroy
; sinon, l'instance et les données ne seront pas récupérées et vous vous retrouverez avec une fuite de mémoire.Et c'est pourquoi ma réponse originale, "Vous ne devriez pas" , est quelque chose à quoi je voudrais m'en tenir.
la source
Il existe un moyen plus simple en tirant parti de l'utilisation des méthodes
bind
etcall
.En définissant des variables privées sur un objet, vous pouvez tirer parti de la portée de cet objet.
Exemple
function TestClass (value) { // The private value(s) var _private = { value: value }; // `bind` creates a copy of `getValue` when the object is instantiated this.getValue = TestClass.prototype.getValue.bind(_private); // Use `call` in another function if the prototype method will possibly change this.getValueDynamic = function() { return TestClass.prototype.getValue.call(_private); }; }; TestClass.prototype.getValue = function() { return this.value; };
Cette méthode n'est pas sans inconvénients. Étant donné que le contexte de portée est effectivement remplacé, vous n'avez pas d'accès en dehors de l'
_private
objet. Cependant, il n'est pas impossible de toujours donner accès à la portée de l'objet d'instance. Vous pouvez passer le contexte de l'objet (this
) comme deuxième argument àbind
oucall
pour avoir toujours accès à ses valeurs publiques dans la fonction prototype.Accéder aux valeurs publiques
function TestClass (value) { var _private = { value: value }; this.message = "Hello, "; this.getMessage = TestClass.prototype.getMessage.bind(_private, this); } TestClass.prototype.getMessage = function(_public) { // Can still access passed in arguments // e.g. – test.getValues('foo'), 'foo' is the 2nd argument to the method console.log([].slice.call(arguments, 1)); return _public.message + this.value; }; var test = new TestClass("World"); test.getMessage(1, 2, 3); // [1, 2, 3] (console.log) // => "Hello, World" (return value) test.message = "Greetings, "; test.getMessage(); // [] (console.log) // => "Greetings, World" (return value)
la source
Essayez-le!
function Potatoe(size) { var _image = new Image(); _image.src = 'potatoe_'+size+'.png'; function getImage() { if (getImage.caller == null || getImage.caller.owner != Potatoe.prototype) throw new Error('This is a private property.'); return _image; } Object.defineProperty(this,'image',{ configurable: false, enumerable: false, get : getImage }); Object.defineProperty(this,'size',{ writable: false, configurable: false, enumerable: true, value : size }); } Potatoe.prototype.draw = function(ctx,x,y) { //ctx.drawImage(this.image,x,y); console.log(this.image); } Potatoe.prototype.draw.owner = Potatoe.prototype; var pot = new Potatoe(32); console.log('Potatoe size: '+pot.size); try { console.log('Potatoe image: '+pot.image); } catch(e) { console.log('Oops: '+e); } pot.draw();
la source
caller
, qui est une extension dépendante de l'implémentation non autorisée en mode strict.Voici ce que j'ai trouvé.
(function () { var staticVar = 0; var yrObj = function () { var private = {"a":1,"b":2}; var MyObj = function () { private.a += staticVar; staticVar++; }; MyObj.prototype = { "test" : function () { console.log(private.a); } }; return new MyObj; }; window.YrObj = yrObj; }()); var obj1 = new YrObj; var obj2 = new YrObj; obj1.test(); // 1 obj2.test(); // 2
le principal problème de cette implémentation est qu'elle redéfinit les prototypes à chaque instanciation.
la source
Il existe un moyen très simple de le faire
function SharedPrivate(){ var private = "secret"; this.constructor.prototype.getP = function(){return private} this.constructor.prototype.setP = function(v){ private = v;} } var o1 = new SharedPrivate(); var o2 = new SharedPrivate(); console.log(o1.getP()); // secret console.log(o2.getP()); // secret o1.setP("Pentax Full Frame K1 is on sale..!"); console.log(o1.getP()); // Pentax Full Frame K1 is on sale..! console.log(o2.getP()); // Pentax Full Frame K1 is on sale..! o2.setP("And it's only for $1,795._"); console.log(o1.getP()); // And it's only for $1,795._
Les prototypes JavaScript sont en or.
la source
SharedPrivate.prototype
carthis.constructor.prototype
ce n'est pas grave de redéfinir getP et setP plusieurs fois ...Je suis en retard à la fête, mais je pense que je peux contribuer. Ici, vérifiez 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 ce modèle d'accesseur de méthode . L'idée essentielle est que nous avons une fermeture , une clé à l'intérieur de la fermeture, et nous créons un objet privé (dans le constructeur) auquel on ne peut accéder que 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 qui ne sont pas accessibles en dehors de la fermeture. Par conséquent, vous pouvez les utiliser dans un constructeur ou un prototype, mais pas ailleurs. Je n'ai vu cette méthode utilisée nulle part, mais je pense que c'est vraiment puissant.
la source
Vous ne pouvez pas placer les variables dans une portée plus élevée?
(function () { var privateVariable = true; var MyClass = function () { if (privateVariable) console.log('readable from private scope!'); }; MyClass.prototype.publicMethod = function () { if (privateVariable) console.log('readable from public scope!'); }; }))();
la source
Vous pouvez également essayer d'ajouter une méthode non pas directement sur le prototype, mais sur une fonction constructeur comme celle-ci:
var MyArray = function() { var array = []; this.add = MyArray.add.bind(null, array); this.getAll = MyArray.getAll.bind(null, array); } MyArray.add = function(array, item) { array.push(item); } MyArray.getAll = function(array) { return array; } var myArray1 = new MyArray(); myArray1.add("some item 1"); console.log(myArray1.getAll()); // ['some item 1'] var myArray2 = new MyArray(); myArray2.add("some item 2"); console.log(myArray2.getAll()); // ['some item 2'] console.log(myArray1.getAll()); // ['some item 2'] - FINE!
la source
Voici quelque chose que j'ai trouvé en essayant de trouver la solution la plus simple à ce problème, peut-être que cela pourrait être utile à quelqu'un. Je suis nouveau sur javascript, donc il pourrait bien y avoir des problèmes avec le code.
// pseudo-class definition scope (function () { // this is used to identify 'friend' functions defined within this scope, // while not being able to forge valid parameter for GetContext() // to gain 'private' access from outside var _scope = new (function () { })(); // ----------------------------------------------------------------- // pseudo-class definition this.Something = function (x) { // 'private' members are wrapped into context object, // it can be also created with a function var _ctx = Object.seal({ // actual private members Name: null, Number: null, Somefunc: function () { console.log('Something(' + this.Name + ').Somefunc(): number = ' + this.Number); } }); // ----------------------------------------------------------------- // function below needs to be defined in every class // to allow limited access from prototype this.GetContext = function (scope) { if (scope !== _scope) throw 'access'; return _ctx; } // ----------------------------------------------------------------- { // initialization code, if any _ctx.Name = (x !== 'undefined') ? x : 'default'; _ctx.Number = 0; Object.freeze(this); } } // ----------------------------------------------------------------- // prototype is defined only once this.Something.prototype = Object.freeze({ // public accessors for 'private' field get Number() { return this.GetContext(_scope).Number; }, set Number(v) { this.GetContext(_scope).Number = v; }, // public function making use of some private fields Test: function () { var _ctx = this.GetContext(_scope); // access 'private' field console.log('Something(' + _ctx.Name + ').Test(): ' + _ctx.Number); // call 'private' func _ctx.Somefunc(); } }); // ----------------------------------------------------------------- // wrap is used to hide _scope value and group definitions }).call(this); function _A(cond) { if (cond !== true) throw new Error('assert failed'); } // ----------------------------------------------------------------- function test_smth() { console.clear(); var smth1 = new Something('first'), smth2 = new Something('second'); //_A(false); _A(smth1.Test === smth2.Test); smth1.Number = 3; smth2.Number = 5; console.log('smth1.Number: ' + smth1.Number + ', smth2.Number: ' + smth2.Number); smth1.Number = 2; smth2.Number = 6; smth1.Test(); smth2.Test(); try { var ctx = smth1.GetContext(); } catch (err) { console.log('error: ' + err); } } test_smth();
la source
J'ai fait face exactement à la même question aujourd'hui et après avoir développé la réponse de première classe de Scott Rippey, j'ai proposé une solution très simple (IMHO) qui est à la fois compatible avec ES5 et efficace, c'est aussi nom clash safe (utiliser _private semble dangereux) .
/*jslint white: true, plusplus: true */ /*global console */ var a, TestClass = (function(){ "use strict"; function PrefixedCounter (prefix) { var counter = 0; this.count = function () { return prefix + (++counter); }; } var TestClass = (function(){ var cls, pc = new PrefixedCounter("_TestClass_priv_") , privateField = pc.count() ; cls = function(){ this[privateField] = "hello"; this.nonProtoHello = function(){ console.log(this[privateField]); }; }; cls.prototype.prototypeHello = function(){ console.log(this[privateField]); }; return cls; }()); return TestClass; }()); a = new TestClass(); a.nonProtoHello(); a.prototypeHello();
Testé avec ringojs et nodejs. J'ai hâte de lire votre opinion.
la source
var getParams = function(_func) { res = _func.toString().split('function (')[1].split(')')[0].split(',') return res } function TestClass(){ var private = {hidden: 'secret'} //clever magic accessor thing goes here if ( !(this instanceof arguments.callee) ) { for (var key in arguments) { if (typeof arguments[key] == 'function') { var keys = getParams(arguments[key]) var params = [] for (var i = 0; i <= keys.length; i++) { if (private[keys[i]] != undefined) { params.push(private[keys[i]]) } } arguments[key].apply(null,params) } } } } TestClass.prototype.test = function(){ var _hidden; //variable I want to get TestClass(function(hidden) {_hidden = hidden}) //invoke magic to get }; new TestClass().test()
Comment c'est? Utilisation d'un accesseur privé. Vous permet uniquement d'obtenir les variables mais de ne pas les définir, cela dépend du cas d'utilisation.
la source
J'ai une solution, mais je ne suis pas sûr qu'elle soit sans défauts.
Pour que cela fonctionne, vous devez utiliser la structure suivante:
Voici le code:
var TestClass = (function () { // difficult to be guessed. var hash = Math.round(Math.random() * Math.pow(10, 13) + + new Date()); var TestClass = function () { var privateFields = { field1: 1, field2: 2 }; this.getPrivateFields = function (hashed) { if(hashed !== hash) { throw "Cannot access private fields outside of object."; // or return null; } return privateFields; }; }; TestClass.prototype.prototypeHello = function () { var privateFields = this.getPrivateFields(hash); privateFields.field1 = Math.round(Math.random() * 100); privateFields.field2 = Math.round(Math.random() * 100); }; TestClass.prototype.logField1 = function () { var privateFields = this.getPrivateFields(hash); console.log(privateFields.field1); }; TestClass.prototype.logField2 = function () { var privateFields = this.getPrivateFields(hash); console.log(privateFields.field2); }; return TestClass; })();
La façon dont cela fonctionne est qu'elle fournit une fonction d'instance "this.getPrivateFields" pour accéder à l'objet de variables privées "privateFields", mais cette fonction ne retournera que l'objet "privateFields" à l'intérieur de la fermeture principale définie (également des fonctions prototypes utilisant "this.getPrivateFields "doivent être définis à l'intérieur de cette fermeture).
Un hachage produit pendant l'exécution et difficile à deviner est utilisé comme paramètre pour s'assurer que même si "getPrivateFields" est appelé en dehors de la portée de la fermeture ne retournera pas l'objet "privateFields".
L'inconvénient est que nous ne pouvons pas étendre TestClass avec plus de fonctions prototypes en dehors de la fermeture.
Voici un code de test:
var t1 = new TestClass(); console.log('Initial t1 field1 is: '); t1.logField1(); console.log('Initial t1 field2 is: '); t1.logField2(); t1.prototypeHello(); console.log('t1 field1 is now: '); t1.logField1(); console.log('t1 field2 is now: '); t1.logField2(); var t2 = new TestClass(); console.log('Initial t2 field1 is: '); t2.logField1(); console.log('Initial t2 field2 is: '); t2.logField2(); t2.prototypeHello(); console.log('t2 field1 is now: '); t2.logField1(); console.log('t2 field2 is now: '); t2.logField2(); console.log('t1 field1 stays: '); t1.logField1(); console.log('t1 field2 stays: '); t1.logField2(); t1.getPrivateFields(11233);
EDIT: En utilisant cette méthode, il est également possible de "définir" des fonctions privées.
TestClass.prototype.privateFunction = function (hashed) { if(hashed !== hash) { throw "Cannot access private function."; } }; TestClass.prototype.prototypeHello = function () { this.privateFunction(hash); };
la source
Je jouais avec ça aujourd'hui et c'était la seule solution que je pouvais trouver sans utiliser de symboles. La meilleure chose à ce sujet est que tout peut être complètement privé.
La solution est basée sur un chargeur de module local qui devient essentiellement le médiateur d'un cache de stockage privé (en utilisant une carte faible).
const loader = (function() { function ModuleLoader() {} //Static, accessible only if truly needed through obj.constructor.modules //Can also be made completely private by removing the ModuleLoader prefix. ModuleLoader.modulesLoaded = 0; ModuleLoader.modules = {} ModuleLoader.prototype.define = function(moduleName, dModule) { if (moduleName in ModuleLoader.modules) throw new Error('Error, duplicate module'); const module = ModuleLoader.modules[moduleName] = {} module.context = { __moduleName: moduleName, exports: {} } //Weak map with instance as the key, when the created instance is garbage collected or goes out of scope this will be cleaned up. module._private = { private_sections: new WeakMap(), instances: [] }; function private(action, instance) { switch (action) { case "create": if (module._private.private_sections.has(instance)) throw new Error('Cannot create private store twice on the same instance! check calls to create.') module._private.instances.push(instance); module._private.private_sections.set(instance, {}); break; case "delete": const index = module._private.instances.indexOf(instance); if (index == -1) throw new Error('Invalid state'); module._private.instances.slice(index, 1); return module._private.private_sections.delete(instance); break; case "get": return module._private.private_sections.get(instance); break; default: throw new Error('Invalid action'); break; } } dModule.call(module.context, private); ModuleLoader.modulesLoaded++; } ModuleLoader.prototype.remove = function(moduleName) { if (!moduleName in (ModuleLoader.modules)) return; /* Clean up as best we can. */ const module = ModuleLoader.modules[moduleName]; module.context.__moduleName = null; module.context.exports = null; module.cotext = null; module._private.instances.forEach(function(instance) { module._private.private_sections.delete(instance) }); for (let i = 0; i < module._private.instances.length; i++) { module._private.instances[i] = undefined; } module._private.instances = undefined; module._private = null; delete ModuleLoader.modules[moduleName]; ModuleLoader.modulesLoaded -= 1; } ModuleLoader.prototype.require = function(moduleName) { if (!(moduleName in ModuleLoader.modules)) throw new Error('Module does not exist'); return ModuleLoader.modules[moduleName].context.exports; } return new ModuleLoader(); })(); loader.define('MyModule', function(private_store) { function MyClass() { //Creates the private storage facility. Called once in constructor. private_store("create", this); //Retrieve the private storage object from the storage facility. private_store("get", this).no = 1; } MyClass.prototype.incrementPrivateVar = function() { private_store("get", this).no += 1; } MyClass.prototype.getPrivateVar = function() { return private_store("get", this).no; } this.exports = MyClass; }) //Get whatever is exported from MyModule const MyClass = loader.require('MyModule'); //Create a new instance of `MyClass` const myClass = new MyClass(); //Create another instance of `MyClass` const myClass2 = new MyClass(); //print out current private vars console.log('pVar = ' + myClass.getPrivateVar()) console.log('pVar2 = ' + myClass2.getPrivateVar()) //Increment it myClass.incrementPrivateVar() //Print out to see if one affected the other or shared console.log('pVar after increment = ' + myClass.getPrivateVar()) console.log('pVar after increment on other class = ' + myClass2.getPrivateVar()) //Clean up. loader.remove('MyModule')
la source
Je sais que cela fait plus d'une décennie que cela a été demandé, mais je viens de réfléchir à cela pour la n-ième fois de ma vie de programmeur et j'ai trouvé une solution possible que je ne sais pas si j'aime encore entièrement . Je n'ai pas vu cette méthodologie documentée auparavant, donc je la nommerai le "modèle dollar privé / public" ou _ $ / $ modèle .
var ownFunctionResult = this.$("functionName"[, arg1[, arg2 ...]]); var ownFieldValue = this._$("fieldName"[, newValue]); var objectFunctionResult = objectX.$("functionName"[, arg1[, arg2 ...]]); //Throws an exception. objectX._$ is not defined var objectFieldValue = objectX._$("fieldName"[, newValue]);
Le concept utilise une fonction ClassDefinition qui renvoie une fonction Constructor qui renvoie un objet Interface . La seule méthode de l'interface est celle
$
qui reçoit unname
argument pour appeler la fonction correspondante dans l'objet constructeur, tous les arguments supplémentaires passés aprèsname
sont passés lors de l'invocation.La fonction d'assistance définie globalement
ClassValues
stocke tous les champs d' un objet selon les besoins. Il définit la_$
fonction pour y accéder parname
. Ceci suit un court modèle get / set donc s'ilvalue
est passé, il sera utilisé comme nouvelle valeur de variable.var ClassValues = function (values) { return { _$: function _$(name, value) { if (arguments.length > 1) { values[name] = value; } return values[name]; } }; };
La fonction définie globalement
Interface
prend un objet et unValues
objet pour renvoyer un_interface
avec une seule fonction$
qui examineobj
pour trouver une fonction nommée d'après le paramètrename
et l'invoque avecvalues
comme objet de portée . Les arguments supplémentaires passés à$
seront transmis lors de l'appel de la fonction.var Interface = function (obj, values, className) { var _interface = { $: function $(name) { if (typeof(obj[name]) === "function") { return obj[name].apply(values, Array.prototype.splice.call(arguments, 1)); } throw className + "." + name + " is not a function."; } }; //Give values access to the interface. values.$ = _interface.$; return _interface; };
Dans l'exemple ci-dessous,
ClassX
est affecté au résultat deClassDefinition
, qui est laConstructor
fonction.Constructor
peut recevoir n'importe quel nombre d'arguments.Interface
est ce que le code externe obtient après avoir appelé le constructeur.var ClassX = (function ClassDefinition () { var Constructor = function Constructor (valA) { return Interface(this, ClassValues({ valA: valA }), "ClassX"); }; Constructor.prototype.getValA = function getValA() { //private value access pattern to get current value. return this._$("valA"); }; Constructor.prototype.setValA = function setValA(valA) { //private value access pattern to set new value. this._$("valA", valA); }; Constructor.prototype.isValAValid = function isValAValid(validMessage, invalidMessage) { //interface access pattern to call object function. var valA = this.$("getValA"); //timesAccessed was not defined in constructor but can be added later... var timesAccessed = this._$("timesAccessed"); if (timesAccessed) { timesAccessed = timesAccessed + 1; } else { timesAccessed = 1; } this._$("timesAccessed", timesAccessed); if (valA) { return "valA is " + validMessage + "."; } return "valA is " + invalidMessage + "."; }; return Constructor; }());
Il est inutile d'avoir des fonctions non prototypées dans
Constructor
, bien que vous puissiez les définir dans le corps de la fonction constructeur. Toutes les fonctions sont appelées avec le modèle de dollar publicthis.$("functionName"[, param1[, param2 ...]])
. Les valeurs privées sont accessibles avec le modèle de dollar privéthis._$("valueName"[, replacingValue]);
. CommeInterface
n'a pas de définition pour_$
, les valeurs ne sont pas accessibles par les objets externes. Puisque chaque corps de fonction prototypéthis
est défini sur l'values
objet dans function$
, vous obtiendrez des exceptions si vous appelez directement les fonctions frères Constructor; le modèle _ $ / $ doit également être suivi dans les corps de fonctions prototypés. Ci-dessous un exemple d'utilisation.var classX1 = new ClassX(); console.log("classX1." + classX1.$("isValAValid", "valid", "invalid")); console.log("classX1.valA: " + classX1.$("getValA")); classX1.$("setValA", "v1"); console.log("classX1." + classX1.$("isValAValid", "valid", "invalid")); var classX2 = new ClassX("v2"); console.log("classX1.valA: " + classX1.$("getValA")); console.log("classX2.valA: " + classX2.$("getValA")); //This will throw an exception //classX1._$("valA");
Et la sortie de la console.
classX1.valA is invalid. classX1.valA: undefined classX1.valA is valid. classX1.valA: v1 classX2.valA: v2
Le modèle _ $ / $ permet une confidentialité totale des valeurs dans des classes entièrement prototypées. Je ne sais pas si j'utiliserai jamais ça, ni s'il a des défauts, mais bon, c'était un bon puzzle!
la source
Cartes faibles ES6
En utilisant un modèle simple basé sur ES6, WeakMaps est possible d'obtenir des variables membres privées, accessibles à partir des fonctions prototypes .
// Create a private scope using an Immediately // Invoked Function Expression... let Person = (function() { // Create the WeakMap that will hold each // Instance collection's of private data let privateData = new WeakMap(); // Declare the Constructor : function Person(name) { // Insert the private data in the WeakMap, // using 'this' as a unique acces Key privateData.set(this, { name: name }); } // Declare a prototype method Person.prototype.getName = function() { // Because 'privateData' is in the same // scope, it's contents can be retrieved... // by using again 'this' , as the acces key return privateData.get(this).name; }; // return the Constructor return Person; }());
Une explication plus détaillée de ce modèle peut être trouvée ici
la source
Vous devez changer 3 choses dans votre code:
var privateField = "hello"
parthis.privateField = "hello"
.privateField
parthis.privateField
.privateField
parthis.privateField
.Le code final serait le suivant:
TestClass = function(){ this.privateField = "hello"; this.nonProtoHello = function(){alert(this.privateField)}; } TestClass.prototype.prototypeHello = function(){alert(this.privateField)}; var t = new TestClass(); t.prototypeHello()
la source
this.privateField
ne serait pas un champ privé. il est accessible de l'extérieur:t.privateField
Vous pouvez utiliser une affectation de prototype dans la définition du constructeur.
La variable sera visible par la méthode prototype ajoutée mais toutes les instances des fonctions accèderont à la même variable SHARED.
function A() { var sharedVar = 0; this.local = ""; A.prototype.increment = function(lval) { if (lval) this.local = lval; alert((++sharedVar) + " while this.p is still " + this.local); } } var a = new A(); var b = new A(); a.increment("I belong to a"); b.increment("I belong to b"); a.increment(); b.increment();
J'espère que cela peut être utile.
la source