D'après ma compréhension de javascript, les méthodes de prototype ne peuvent pas accéder à des variables qui sont privées à la portée du constructeur,

 var Foo = function() {
      var myprivate = 'I am private';    
      this.mypublic = 'I am public';
 }

 Foo.prototype = {
     alertPublic: function() { alert(this.mypublic); } // will work
     alertPrivate: function() { alert(myprivate); } // won't work
 }

Cela est parfaitement logique, mais y a-t-il un moyen de contourner cela qui soit une bonne pratique sûre? Étant donné que l'utilisation de prototypes offre un avantage en termes de performances dans la mesure où les fonctions membres ne sont allouées qu'une seule fois, j'aimerais obtenir une fonctionnalité similaire tout en étant en mesure d'accéder à mes variables privées. Je ne pense pas que cela fonctionnera en utilisant un prototype, mais y a-t-il un autre modèle, comme une méthode d'usine ou une approche de fermeture? Quelque chose comme,

var fooFactory = function() {
    var _alertPrivate = function(p) { alert(p); } // bulk of the logic goes here
    return function(args) {
         var foo = {}; 
         var myprivate = args.someVar; 
         foo.mypublic = args.someOtherVar; 
         foo.alertPrivate = function() { _alertPrivate(myprivate); };
         return foo; 
    }; 
}

var makeFoo = new fooFactory();
var foo = makeFoo(args); 

Je ne sais pas si une nouvelle copie de _alertPrivate est créée chaque fois que je crée un nouveau Foo ou s'il y a un avantage potentiel en termes de performances. L'intention est d'obtenir une fonctionnalité similaire au prototypage (dans la mesure où elle économise de la mémoire) tout en pouvant accéder à des variables privées.

Merci.

8
Sean Thoman 17 oct. 2011 à 03:52

3 réponses

Meilleure réponse

J'ai trouvé le modèle suivant pour résoudre ce problème, du moins pour l'instant. Ce dont j'avais besoin était un setter privilégié afin qu'une variable privée puisse être modifiée depuis l'intérieur de certaines fonctions prototypes mais pas depuis n'importe où ailleurs:

 var Foo = (function() {

    // the bulk of the objects behavior goes here and is created once 
    var functions = {
        update: function(a) {
             a['privateVar'] = "Private variable set from the prototype";
        }
    }; 

    // the objects prototype, also created once
    var proto = {
        Update: function() {
             this.caller('update'); 
        }
    };

    // special function to get private vars into scope
    var hoist = function(accessor) {
        return function(key) {
             return functions[key](accessor()); 
        }
    }

    // the constructor itself
    var foo = function foo() {
        var state = {
            privateVar: "Private variable set in constructor",
            // put more private vars here
        }
        this.caller = hoist(function(){
            return state;
        }); 
    }

    // assign the prototype
    foo.prototype = proto;

    // return the constructor
    return foo; 

 })(); 

Fondamentalement, un pointeur vers l'état interne des objets est hissé vers son prototype via une fermeture sur une fonction d'accesseur simple () {état de retour; }. L'utilisation de la fonction «appelant» sur une instance donnée vous permet d'appeler des fonctions qui ne sont créées qu'une seule fois mais peuvent toujours faire référence à l'état privé détenu dans cette instance. Il est également important de noter qu'aucune fonction en dehors du prototype ne pourra jamais accéder à l'accesseur privilégié, car «l'appelant» n'accepte qu'une clé renvoyant aux fonctions prédéfinies qui sont dans le champ d'application.

Voici quelques repères de cette méthode pour voir comment elle se compare au prototypage pur. Ces chiffres représentent la création de 80 000 instances de l'objet dans une boucle (notez que l'objet utilisé pour l'analyse comparative est plus complexe que celui ci-dessus, qui était juste à des fins de simplification):

CHROME:

Fermeture uniquement - 2172 ms

Prototypage (ci-dessus) - 822 ms

Prototypage (voie standard) - 751 ms

FIREFOX:

Fermeture uniquement - 1528 ms

Prototypage (au dessus) - 971ms

Prototypage (voie standard) - 752 ms

Comme vous pouvez le voir, la méthode est presque aussi rapide que le prototypage normal, et certainement plus rapide que l'utilisation d'une fermeture normale qui copie les fonctions avec l'instance.

4
Sean Thoman 18 oct. 2011 à 18:58

Ce que vous demandez est possible, mais il y aura toujours un compromis entre les performances (en vitesse ou en mémoire) et la fonctionnalité.

En JavaScript, il est possible d'obtenir un état privé par instance, avec des méthodes de prototypage normales (et sans stockage centralisé, qui fuit, sur le terrain).

Consultez l'article que j'ai écrit sur la technique: http://www.codeproject.com/KB/ ajax / SafeFactoryPattern.aspx

Ou accédez directement au code source dans: https://github.com/dcleao/private-state.

1
Duarte Cunha Leão 18 juil. 2014 à 00:16

J'ai trouvé la réponse de Sean Thoman très utile (bien que difficile à comprendre au début).

Il ne semblait pas que le setter public puisse accepter une valeur pour privateVar donc j'ai fait quelques ajustements:

Modifiez update en functions:

update: function(st, newVal) {
     st['privateVar'] = newVal;
}

Modifiez Update dans le proto:

Update: function(newVal) {
     this.caller('update', newVal); 
}

Modifiez hoist:

var hoist = function(accessor) {
    return function(key) {
        // we can't slice arguments directly because it's not a real array
        var args_tail = Array.prototype.slice.call(arguments, 1);
        return functions[key].apply(functions[key], [accessor()].concat(args_tail)); 
    }
}
1
Kelvin 9 mai 2013 à 16:29