web-dev-qa-db-fra.com

Accès aux variables de membre privées à partir de fonctions définies par un prototype

Existe-t-il un moyen de rendre les variables «privées» (celles définies dans le constructeur) disponibles pour les méthodes définies par un prototype?

TestClass = function(){
    var privateField = "hello";
    this.nonProtoHello = function(){alert(privateField)};
};
TestClass.prototype.prototypeHello = function(){alert(privateField)};

Cela marche:

t.nonProtoHello()

Mais cela ne veut pas:

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.

176
morgancodes

Non, il n'y a pas moyen de le faire. Ce serait essentiellement en sens 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'objet this, auxquels les méthodes du prototype (ainsi que tout le reste) aura aura 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()); };
175
Triptych

Mise à jour: Avec ES6, il existe un meilleur moyen:

En résumé, vous pouvez utiliser la nouvelle Symbol pour créer des champs privés.
Voici une description géniale: 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 seulement des fermetures

Le moyen le plus simple de construire des objets consiste à éviter complètement l'héritage prototypique. Il suffit de définir 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 prototypal est principalement une optimisation _. Cela permet à plusieurs instances de partager des méthodes prototypes, au lieu que chaque instance ait ses propres méthodes.
L’inconvénient est que this est la chose seulement qui est différente chaque fois qu’une fonction prototype 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 pour les champs _private.

Ne vous embêtez pas dans le mélange de fermetures avec des prototypes

Je pense que vous ne devriez pas associer des variables de fermeture à des méthodes prototypes. Vous devriez 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 sur 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 est-ce que je choisis?

Pour des objets vraiment simples, utilisez simplement un objet simple avec des fermetures. 

Si vous avez besoin d'héritage prototype - héritage, performances, etc. - respectez la convention de dénomination "_private" et ne vous occupez pas des fermetures.

Je ne comprends pas pourquoi les développeurs JS essaient SO de rendre les champs réellement privés. 

60
Scott Rippey

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’était_ _ CRAAAAZYmais cela fonctionne totalement.

Premièrement, j'ai essayé de définir la classe dans une fonction immédiate afin que vous ayez accès à certaines 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 rapidement que tous les objets partagent 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);

Dans de nombreux cas, cela serait approprié, par exemple si vous souhaitez utiliser des valeurs constantes telles que les noms d'événements partagés entre les instances. Mais essentiellement, elles agissent comme des variables statiques privées.

Si vous avez absolument besoin d'accéder aux variables d'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 recevoir des commentaires de tous ceux qui voient une erreur dans cette façon de faire.

30
Mims H. Wright

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
18
Jason S

Je suggère que ce serait probablement une bonne idée de décrire "avoir un prototype d'assignation dans un constructeur" comme un anti-motif Javascript. Penses-y. C'est trop risqué.

Lors de la création du deuxième objet (c.-à-d. B), vous redéfinissez cette fonction prototype pour tous les objets utilisant 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 beaucoup trop risqué.

J'ai trouvé un bug dans un Javascript sur lequel je travaillais récemment et qui était dû à cet anti-motif exact. Il essayait de définir un gestionnaire glisser-déposer sur l'objet en cours de création mais le faisait plutôt pour toutes les instances. Pas bon.

La solution de Doug Crockford est la meilleure.

15
Lance Ewing

@ Kai

Ça ne marchera pas. Si tu fais

var t2 = new TestClass();

alors t2.prototypeHello sera en train d'accéder à la section privée de t.

@AnglesCrimes

L'exemple de code fonctionne bien, mais il crée en réalité 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 facile et propre de le faire sans introduire un hachage privé et des fonctions de nettoyage supplémentaires. Une fonction membre privée 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;
}());
10
Tim

Oui c'est possible. Le modèle de conception PPF résout ce problème. 

PPF signifie Private Prototype Functions. Le PPF de base résout ces problèmes:

  1. Les fonctions prototypes ont accès aux données d'instance privées.
  2. Les fonctions prototypes peuvent être rendues privées.

Pour le premier, juste:

  1. Placez toutes les variables d'instance privées que vous souhaitez rendre accessibles à partir de fonctions de prototype dans un conteneur de données séparé, et
  2. Passez une référence au conteneur de données à toutes les fonctions de prototype en tant que paramètre.

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

6
Edward

Vous pouvez y parvenir en utilisant Accessor Verification:

(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 Fonctions de prototype et données privées et est expliqué plus en détail ici.

4
Chris West

Dans le code JavaScript actuel, je suis presque certain qu'il existe un et un seul moyen d'avoir état privé, accessible à partir de prototype fonctions, sans rien ajouter _ {public à this. La réponse consiste à utiliser le modèle "carte faible".

Pour résumer: la classe Person a une seule carte faible, où les clés sont les instances de Person et les valeurs sont des objets simples utilisés pour le stockage privé.

Voici un exemple entièrement fonctionnel: (jouez à 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 le disais, c’est vraiment le seul moyen de réaliser les 3 parties. 

Il y a cependant deux mises en garde. Tout d’abord, cela coûte de la performance: chaque fois que vous accédez à des données privées, c’est une opération O(n), où n est le nombre d’instances. Donc, vous ne voudrez pas faire cela si vous avez un grand nombre d'instances ..__ Deuxièmement, lorsque vous avez terminé avec une instance, vous devez appeler destroy; sinon, l'instance et les données ne seront pas récupérées et vous obtiendrez une fuite de mémoire. 

Et c’est pourquoi ma réponse initiale, "Vous ne devriez pas", est une chose sur laquelle je voudrais rester.

4
Scott Rippey

Il existe un moyen plus simple de tirer parti des méthodes bind et call.

En définissant des variables privées sur un objet, vous pouvez exploiter 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 la portée est effectivement remplacé, vous n’avez pas accès en dehors de l’objet _private. Cependant, il n'est pas impossible de donner quand même accès à la portée de l'objet d'instance. Vous pouvez passer dans le contexte de l'objet (this) comme deuxième argument de bind ou call pour toujours avoir 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)
3
thgaskell

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();
2
AlanNLohse

Voici ce que je suis venu avec.

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

1
Xeltor

Il y a un moyen très simple de faire cela

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.

1
Redu

Je suis en retard à la fête, mais je pense pouvoir 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 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

Je sais que cela fait plus de dix ans que cette question a été posée, mais je ne fais que penser à cela pour la n-ème fois de ma vie de programmeur et ai trouvé une solution possible que je ne sais pas si j'aime encore tout à fait. . Je n'ai jamais vu cette méthodologie documentée auparavant, je vais donc l'appeler "modèle de dollar privé/public" ou_ $/$ pattern.

var ownFunctionResult = this.$("functionName"[, arg1[, arg2 ...]]);
var ownFieldValue = this._$("fieldName"[, newValue]);

var objectFunctionResult = objectX.$("functionName"[, arg1[, arg2 ...]]);

//Throws an exception. this._$ is not defined
var objectFieldValue = this._$("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 $ qui reçoit un argument name pour appeler la fonction correspondante dans l'objet constructeur. Tous les arguments supplémentaires passés après name sont passés dans l'invocation.

La fonction d'assistance définie globalement ClassValues stocke tous les champs de l'objet an selon les besoins. Il définit la fonction _$ pour y accéder par name. Cela fait suite à un modèle get/set court, donc si value 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 un objet Values pour renvoyer un _interface avec une seule fonction $ qui examine obj pour rechercher une fonction nommée d'après le paramètre name et l'appelle avec values comme objet scoped. 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 de ClassDefinition, qui est la fonction Constructor. Constructor peut recevoir un nombre quelconque 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 public dollarthis.$("functionName"[, param1[, param2 ...]]). Les valeurs privées sont accessibles avec le modèle private dollarthis._$("valueName"[, replacingValue]);. Comme Interface n'a pas de définition pour _$, les valeurs ne sont pas accessibles aux objets externes. Dans la mesure où this de chaque corps de fonction prototype est défini sur l'objet values dans la fonction $, vous obtiendrez des exceptions si vous appelez directement les fonctions frères du constructeur; le modèle_ $/$ patterndoit également être suivi dans les corps de fonctions prototypés. Sous utilisation de l'échantillon.

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 les classes entièrement prototypées. Je ne sais pas si je l'utiliserai jamais, ni s'il y a des défauts, mais bon, c'était un bon casse-tête!

0
JPortillo

Ne pouvez-vous pas placer les variables dans un champ plus élevé?

(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!');
    };
}))();
0
Ev Haus
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, dépend du cas d’utilisation.

0
dylan0150

Je jouais avec ça aujourd'hui et c'était la seule solution que je pouvais trouver sans utiliser les Symboles. La meilleure chose à ce sujet est qu’il peut effectivement être totalement privé.

La solution est basée sur un chargeur de module local qui devient le médiateur d’un cache de stockage privé (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')
0
Eladian

ES6 WeakMaps

En utilisant un modèle simple basé sur ES6 WeakMaps , il est possible d’obtenir des variables membres privées, accessibles à partir des fonctions prototypes .

Remarque: L'utilisation de WeakMaps garantit la sécurité contre les fuites de mémoire en laissant le Garbage Collector identifier et ignorer les instances inutilisées.

// 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

0
colxi

Vous pouvez également essayer d’ajouter une méthode non pas directement sur le prototype, mais sur la fonction constructeur comme ceci:

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!
0
Maciej Dzikowicki

Voici quelque chose que j'ai trouvé en essayant de trouver la solution la plus simple pour ce problème, cela pourrait peut-être être utile à quelqu'un. Je suis nouveau dans le javascript, il pourrait donc y avoir quelques 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();
0
V.Mihaly4

Aujourd'hui, après avoir précisé la réponse de première classe de Scott Rippey, je suis arrivé à une solution très simple (IMHO) à la fois compatible avec ES5 et efficace. .

/*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.

0
alexgirao

J'ai une solution, mais je ne suis pas sûre que ce soit sans défauts.

Pour que cela fonctionne, vous devez utiliser la structure suivante:

  1. Utilisez 1 objet privé contenant toutes les variables privées.
  2. Utilisez 1 fonction d'instance.
  3. Appliquez une fermeture au constructeur et à toutes les fonctions du prototype.
  4. Toute instance créée est faite en dehors de la fermeture définie.

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

Comment cela fonctionne, c'est qu'il fournit une fonction d'instance "this.getPrivateFields" pour accéder à l'objet de variables privées "privateFields", mais cette fonction ne renverra que l'objet "privateFields" dans la fermeture principale définie (également des fonctions de prototype utilisant "this.getPrivateFields "doivent être définis dans 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 zone de fermeture, l'objet "privateFields" ne sera pas renvoyé.

L'inconvénient est que nous ne pouvons pas étendre TestClass avec davantage 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);
};
0
Jannes Botis