Méthodes privées JavaScript

482

Pour créer une classe JavaScript avec une méthode publique, je ferais quelque chose comme:

function Restaurant() {}

Restaurant.prototype.buy_food = function(){
   // something here
}

Restaurant.prototype.use_restroom = function(){
   // something here
}

De cette façon, les utilisateurs de ma classe peuvent:

var restaurant = new Restaurant();
restaurant.buy_food();
restaurant.use_restroom();

Comment créer une méthode privée qui peut être appelée par les méthodes buy_foodet use_restroommais pas en externe par les utilisateurs de la classe?

En d'autres termes, je veux que mon implémentation de méthode puisse faire:

Restaurant.prototype.use_restroom = function() {
   this.private_stuff();
}

Mais cela ne devrait pas fonctionner:

var r = new Restaurant();
r.private_stuff();

Comment puis-je définir private_stuffcomme une méthode privée afin que les deux se vérifient?

J'ai lu le texte de Doug Crockford à quelques reprises, mais il ne semble pas que les méthodes "privées" puissent être appelées par des méthodes publiques et les méthodes "privilégiées" puissent être appelées en externe.

Wayne Kao
la source

Réponses:

403

Vous pouvez le faire, mais l'inconvénient est qu'il ne peut pas faire partie du prototype:

function Restaurant() {
    var myPrivateVar;

    var private_stuff = function() {  // Only visible inside Restaurant()
        myPrivateVar = "I can set this here!";
    }

    this.use_restroom = function() {  // use_restroom is visible to all
        private_stuff();
    }

    this.buy_food = function() {   // buy_food is visible to all
        private_stuff();
    }
}
17 sur 26
la source
9
Cacher les minces à l'intérieur de la fermeture ne garantit pas l'intimité de tous les interprètes. Voir code.google.com/p/google-caja/wiki/…
Mike Samuel
51
@mikesamuel - vrai, mais seulement lorsque ces interprètes contiennent des bugs :)
jvenema
133
C'est une méthode privée, bien sûr, mais aura tendance à utiliser beaucoup plus de mémoire qu'une méthode prototype habituelle, surtout si vous créez beaucoup de ces objets. Pour chaque instance d'objet, il crée une fonction distincte liée à l'objet et non à la classe. En outre, cela ne récupère pas les ordures jusqu'à ce que l'objet lui-même soit détruit.
Arindam
4
Si vous faites un objet McDonalds () hériter de Restaurant () McDonalds ne peut pas remplacer les méthodes privées si vous les déclarez de cette façon. Eh bien, en fait, vous pouvez, mais cela ne fonctionnera pas si une autre méthode appelle la méthode private, elle appellera la version originale de la méthode et vous ne pouvez pas non plus appeler la méthode parent. Jusqu'à présent, je n'ai pas trouvé un bon moyen de déclarer des méthodes privées qui fonctionnent bien avec l'héritage. Cela et les implications en termes de performances ne font pas du tout un très bon modèle de conception. Je recommanderais de faire une sorte de préfixe pour indiquer les méthodes privées, comme commencer par un soulignement.
Hoffmann
68
Les méthodes privées ne sont pas censées être remplacées - elles sont privées.
17 du 26
163

Utilisation de la fonction et de l'appel automatique

JavaScript utilise des prototypes et n'a pas de classes (ou de méthodes d'ailleurs) comme les langages orientés objet. Un développeur JavaScript doit penser en JavaScript.

Citation de Wikipedia:

Contrairement à de nombreux langages orientés objet, il n'y a pas de distinction entre une définition de fonction et une définition de méthode. Au contraire, la distinction se produit lors de l'appel de fonction; lorsqu'une fonction est appelée en tant que méthode d'un objet, le mot-clé local this de la fonction est lié à cet objet pour cette invocation.

Solution utilisant une fonction auto-invoquante et la fonction call pour appeler la "méthode" privée:

var MyObject = (function () {

    // Constructor
    function MyObject (foo) {
        this._foo = foo;
    }

    function privateFun (prefix) {
        return prefix + this._foo;
    }

    MyObject.prototype.publicFun = function () {
        return privateFun.call(this, '>>');
    }

    return MyObject;
})();


var myObject = new MyObject('bar');
myObject.publicFun();      // Returns '>>bar'
myObject.privateFun('>>'); // ReferenceError: private is not defined

La fonction d'appel nous permet d'appeler la fonction privée avec le contexte approprié ( this).


Plus simple avec Node.js

Si vous utilisez node.js , vous n'avez pas besoin de l' IIFE car vous pouvez profiter du système de chargement des modules :

function MyObject (foo) {
    this._foo = foo;
}

function privateFun (prefix) {
    return prefix + this._foo;
}

MyObject.prototype.publicFun = function () {
    return privateFun.call(this, '>>');
}

exports.MyObject = MyObject;

Chargez le fichier:

var MyObject = require('./MyObject').MyObject;

var myObject = new MyObject('bar');
myObject.publicFun();      // Returns '>>bar'
myObject.privateFun('>>'); // ReferenceError: private is not defined


(expérimental) ES7 avec l'opérateur Bind

L'opérateur de liaison ::est une proposition ECMAScript et est implémenté dans Babel ( étape 0 ).

export default class MyObject {
  constructor (foo) {
    this._foo = foo;
  }

  publicFun () {
    return this::privateFun('>>');
  }
}

function privateFun (prefix) {
  return prefix + this._foo;
}

Chargez le fichier:

import MyObject from './MyObject';

let myObject = new MyObject('bar');
myObject.publicFun();      // Returns '>>bar'
myObject.privateFun('>>'); // TypeError: myObject.privateFun is not a function
Yves M.
la source
34
C'est la meilleure réponse. Les avantages des méthodes privées, pas de camelote.
TaylorMac
1
@TaylorMac Sauf pour la .callpartie.
pishpish
1
@janje Huh? C'est le point de la question, il n'y a pas de privé f()en javascript (pas pour le moment).
Yves M.
2
@YvesM. Le point de la question est de choisir le meilleur motif qui le simule.
pishpish
1
@changed quel est le meilleur compromis pour avoir des fonctions cachées (non appelables de l'extérieur), une belle sintax (non .call) et une petite mémoire (pas de fonctions sur l'instance)? Cela existe-t-il même?
Ciprian Tomoiagă
161

Vous pouvez simuler des méthodes privées comme celle-ci:

function Restaurant() {
}

Restaurant.prototype = (function() {
    var private_stuff = function() {
        // Private code here
    };

    return {

        constructor:Restaurant,

        use_restroom:function() {
            private_stuff();
        }

    };
})();

var r = new Restaurant();

// This will work:
r.use_restroom();

// This will cause an error:
r.private_stuff();

Plus d'informations sur cette technique ici: http://webreflection.blogspot.com/2008/04/natural-javascript-private-methods.html

georgebrock
la source
7
Je suggère également le site de Douglas Crockford comme ressource sur les méthodes privées / publiques et les membres javascript.crockford.com/private.html
Jared
10
Il a mentionné ce lien dans la question.
Gulzar Nazim
8
L'inconvénient de cette méthode est que vous ne pouvez pas permettre à private_stuff () d'accéder à d'autres données privées dans Restaurant et que les autres méthodes de Restaurant ne peuvent pas appeler private_stuff (). L'avantage est que si vous n'avez besoin d'aucune des conditions que je viens de mentionner, vous pouvez conserver use_restroom () dans le prototype.
17 du 26
6
Cela devrait être la solution et la réponse acceptée car l'auteur utilise clairement la propriété prototype.
Gabriel Llamas
23
Avec le modèle proposé par @georgebrock, toutes les données privées seront partagées entre tous les objets du restaurant. Cela s'apparente aux variables et fonctions privées statiques dans la POO basée sur les classes. Je suppose que l'OP n'en veut pas . Pour illustrer ce que je veux dire, j'ai créé un jsFiddle .
feklee
35

Dans ces situations, lorsque vous avez une API publique et que vous souhaitez des méthodes / propriétés privées et publiques, j'utilise toujours le modèle de module. Ce modèle a été rendu populaire dans la bibliothèque YUI, et les détails peuvent être trouvés ici:

http://yuiblog.com/blog/2007/06/12/module-pattern/

C'est vraiment simple et facile à comprendre pour les autres développeurs. Pour un exemple simple:

var MYLIB = function() {  
    var aPrivateProperty = true;
    var aPrivateMethod = function() {
        // some code here...
    };
    return {
        aPublicMethod : function() {
            aPrivateMethod(); // okay
            // some code here...
        },
        aPublicProperty : true
    };  
}();

MYLIB.aPrivateMethod() // not okay
MYLIB.aPublicMethod() // okay

la source
ce genre de chose ne serait pas détecté par la saisie semi-automatique d'un IDE :(
Cliquez sur Upvote
19
Mais ce n'est pas une classe, donc vous ne pouvez pas avoir 2 "instances" de ceci avec des états différents.
DevAntoine
supprimez la partie () et vous avez une "classe". au moins où vous pouvez instancier différentes isntances avec différents états. le modèle de module est assez gourmand en mémoire, cependant ...
oligofren
@DevAntoine Regardez les commentaires pour la réponse de 17 sur 26. En JavaScript, les classes extensibles et les méthodes privées ne vont pas facilement de pair. Ma suggestion dans ce cas serait de privilégier la composition plutôt que l'héritage. Créez un prototype extensible avec les mêmes méthodes que l'objet en béton inclus. Ensuite, en interne à votre prototype, vous pouvez décider quand appeler des méthodes sur votre objet concret.
Y a-t-il un inconvénient à appeler les variables publiques à partir des fonctions privées comme ceci aPrivateMethod = function() { MYLIB.aPublicProperty}:?
Hanna
21

Voici le cours que j'ai créé pour comprendre ce que Douglas Crockford a suggéré dans son site Private Members in JavaScript

function Employee(id, name) { //Constructor
    //Public member variables
    this.id = id;
    this.name = name;
    //Private member variables
    var fName;
    var lName;
    var that = this;
    //By convention, we create a private variable 'that'. This is used to     
    //make the object available to the private methods. 

    //Private function
    function setFName(pfname) {
        fName = pfname;
        alert('setFName called');
    }
    //Privileged function
    this.setLName = function (plName, pfname) {
        lName = plName;  //Has access to private variables
        setFName(pfname); //Has access to private function
        alert('setLName called ' + this.id); //Has access to member variables
    }
    //Another privileged member has access to both member variables and private variables
    //Note access of this.dataOfBirth created by public member setDateOfBirth
    this.toString = function () {
        return 'toString called ' + this.id + ' ' + this.name + ' ' + fName + ' ' + lName + ' ' + this.dataOfBirth; 
    }
}
//Public function has access to member variable and can create on too but does not have access to private variable
Employee.prototype.setDateOfBirth = function (dob) {
    alert('setDateOfBirth called ' + this.id);
    this.dataOfBirth = dob;   //Creates new public member note this is accessed by toString
    //alert(fName); //Does not have access to private member
}
$(document).ready()
{
    var employee = new Employee(5, 'Shyam'); //Create a new object and initialize it with constructor
    employee.setLName('Bhaskar', 'Ram');  //Call privileged function
    employee.setDateOfBirth('1/1/2000');  //Call public function
    employee.id = 9;                     //Set up member value
    //employee.setFName('Ram');  //can not call Private Privileged method
    alert(employee.toString());  //See the changed object

}
Sarath
la source
5
Le qui = c'est un modèle qui est assez courant, popularisé par Crockford susmentionné dans son livre "Javascript: The good parts"
oligofren
8
thatest utilisé au lieu de thispour éviter les problèmes de portée, lorsque les fonctions sont liées à un autre objet. Ici, vous êtes stockez thisdans thatet ne jamais l' utiliser à nouveau ce qui est la même chose que ne pas le faire du tout. Vous devez changer thisavec thattoutes les Constructorfonctions internes (pas la déclaration de méthodes). Si employeeest applyédité ou callédité d'une manière ou d'une autre, ces méthodes peuvent être renvoyées car thiselles seront liées de manière incorrecte.
Maroshii
De plus, chaque instance aura une copie complète des fonctions privées, inefficace. Cela, en plus du fait que les méthodes publiques ne peuvent pas accéder aux classes privées, me donne envie de passer au dart. Malheureusement angulardart est super beta.
Ray Foss
Où est la méthode "constructeur" dans tout cela? Où mettrais-je une logique qui serait normalement exécutée dans la méthode constructeur d'une classe lorsqu'elle est instanciée?
BadHorsie
13

J'ai évoqué ceci: EDIT: En fait, quelqu'un s'est lié à une solution identique. Duh!

var Car = function() {
}

Car.prototype = (function() {
    var hotWire = function() {
        // Private code *with* access to public properties through 'this'
        alert( this.drive() ); // Alerts 'Vroom!'
    }

    return {
        steal: function() {
            hotWire.call( this ); // Call a private method
        },
        drive: function() {
            return 'Vroom!';
        }
    };
})();

var getAwayVechile = new Car();

hotWire(); // Not allowed
getAwayVechile.hotWire(); // Not allowed
getAwayVechile.steal(); // Alerts 'Vroom!'

la source
1
C'est une belle technique, mais comment autoriseriez-vous les paramètres dans votre constructeur? Par exemple, var getAwayVehicle = new Car(100);100est la vitesse et vous souhaitez alerter la vitesse. Merci!
Jason
1
Compris, peut avoir var Car = function(speed) { this.speed = speed; }et `retourner {constructeur: voiture, ... '
Jason
11

Je pense que de telles questions reviennent encore et encore en raison du manque de compréhension des fermetures. Les fermetures sont la chose la plus importante dans JS. Chaque programmeur JS doit en ressentir l'essence.

1. Tout d'abord, nous devons établir un champ d'application distinct (clôture).

function () {

}

2. Dans ce domaine, nous pouvons faire ce que nous voulons. Et personne ne le saura.

function () {
    var name,
        secretSkills = {
            pizza: function () { return new Pizza() },
            sushi: function () { return new Sushi() }
        }

    function Restaurant(_name) {
        name = _name
    }
    Restaurant.prototype.getFood = function (name) {
        return name in secretSkills ? secretSkills[name]() : null
    }
}

3. Pour que le monde connaisse notre classe de restaurant, nous devons le renvoyer de la fermeture.

var Restaurant = (function () {
    // Restaurant definition
    return Restaurant
})()

4. A la fin, nous avons:

var Restaurant = (function () {
    var name,
        secretSkills = {
            pizza: function () { return new Pizza() },
            sushi: function () { return new Sushi() }
        }

    function Restaurant(_name) {
        name = _name
    }
    Restaurant.prototype.getFood = function (name) {
        return name in secretSkills ? secretSkills[name]() : null
    }
    return Restaurant
})()

5. En outre, cette approche a un potentiel d'héritage et de modèle

// Abstract class
function AbstractRestaurant(skills) {
    var name
    function Restaurant(_name) {
        name = _name
    }
    Restaurant.prototype.getFood = function (name) {
        return skills && name in skills ? skills[name]() : null
    }
    return Restaurant
}

// Concrete classes
SushiRestaurant = AbstractRestaurant({ 
    sushi: function() { return new Sushi() } 
})

PizzaRestaurant = AbstractRestaurant({ 
    pizza: function() { return new Pizza() } 
})

var r1 = new SushiRestaurant('Yo! Sushi'),
    r2 = new PizzaRestaurant('Dominos Pizza')

r1.getFood('sushi')
r2.getFood('pizza')

J'espère que cela aide quelqu'un à mieux comprendre ce sujet

imos
la source
2
Ce que vous avez au point 4. est merveilleux! Je pense que c'est la seule réponse de toutes celles ici où vous obtenez à la fois les gains de performances / mémoire de l'utilisation des méthodes sur le prototype ET ces méthodes publiques ont un accès complet aux membres privés. +1
Hudon
7
Ça ne marche pas. La variable de nom agit ici comme une variable statique et est partagée par toutes les instances de Restaurant. Voici jsbin: jsbin.com/oqewUWa/2/edit?js,output
Shital Shah
c'est un bon essai, mais comme l'a souligné Shital, la variable de nom est boguée.
oligofren
2
ajouter mon 2c ici pour réaffirmer que cela ne fonctionne pas, semble agréable, mais comme indiqué ci-dessus, "nom" servira de variable statique, c'est-à-dire partagé entre toutes les instances
Paul Carroll
10

Personnellement, je préfère le modèle suivant pour créer des classes en JavaScript:

var myClass = (function() {
    // Private class properties go here

    var blueprint = function() {
        // Private instance properties go here
        ...
    };

    blueprint.prototype = { 
        // Public class properties go here
        ...
    };

    return  {
         // Public class properties go here
        create : function() { return new blueprint(); }
        ...
    };
})();

Comme vous pouvez le voir, il vous permet de définir à la fois des propriétés de classe et des propriétés d'instance, chacune pouvant être publique et privée.


Démo

var Restaurant = function() {
    var totalfoodcount = 0;        // Private class property
    var totalrestroomcount  = 0;   // Private class property
    
    var Restaurant = function(name){
        var foodcount = 0;         // Private instance property
        var restroomcount  = 0;    // Private instance property
        
        this.name = name
        
        this.incrementFoodCount = function() {
            foodcount++;
            totalfoodcount++;
            this.printStatus();
        };
        this.incrementRestroomCount = function() {
            restroomcount++;
            totalrestroomcount++;
            this.printStatus();
        };
        this.getRestroomCount = function() {
            return restroomcount;
        },
        this.getFoodCount = function() {
            return foodcount;
        }
    };
   
    Restaurant.prototype = {
        name : '',
        buy_food : function(){
           this.incrementFoodCount();
        },
        use_restroom : function(){
           this.incrementRestroomCount();
        },
        getTotalRestroomCount : function() {
            return totalrestroomcount;
        },
        getTotalFoodCount : function() {
            return totalfoodcount;
        },
        printStatus : function() {
           document.body.innerHTML
               += '<h3>Buying food at '+this.name+'</h3>'
               + '<ul>' 
               + '<li>Restroom count at ' + this.name + ' : '+ this.getRestroomCount() + '</li>'
               + '<li>Food count at ' + this.name + ' : ' + this.getFoodCount() + '</li>'
               + '<li>Total restroom count : '+ this.getTotalRestroomCount() + '</li>'
               + '<li>Total food count : '+ this.getTotalFoodCount() + '</li>'
               + '</ul>';
        }
    };

    return  { // Singleton public properties
        create : function(name) {
            return new Restaurant(name);
        },
        printStatus : function() {
          document.body.innerHTML
              += '<hr />'
              + '<h3>Overview</h3>'
              + '<ul>' 
              + '<li>Total restroom count : '+ Restaurant.prototype.getTotalRestroomCount() + '</li>'
              + '<li>Total food count : '+ Restaurant.prototype.getTotalFoodCount() + '</li>'
              + '</ul>'
              + '<hr />';
        }
    };
}();

var Wendys = Restaurant.create("Wendy's");
var McDonalds = Restaurant.create("McDonald's");
var KFC = Restaurant.create("KFC");
var BurgerKing = Restaurant.create("Burger King");

Restaurant.printStatus();

Wendys.buy_food();
Wendys.use_restroom();
KFC.use_restroom();
KFC.use_restroom();
Wendys.use_restroom();
McDonalds.buy_food();
BurgerKing.buy_food();

Restaurant.printStatus();

BurgerKing.buy_food();
Wendys.use_restroom();
McDonalds.buy_food();
KFC.buy_food();
Wendys.buy_food();
BurgerKing.buy_food();
McDonalds.buy_food();

Restaurant.printStatus();

Voir aussi ce violon .

John Slegers
la source
Cela me donne envie d'utiliser les classes es6 et de voir ce que cela transpile aussi
sheriffderek
9

Toute cette fermeture vous coûtera. Assurez-vous de tester les implications de vitesse, en particulier dans IE. Vous constaterez que vous êtes mieux avec une convention de dénomination. Il y a encore beaucoup d'utilisateurs Web d'entreprise qui sont obligés d'utiliser IE6 ...

Kelly
la source
34
Qui s'en soucie, sérieusement?
nowayyy
17
Les 9% qui utilisent encore IE6 ne se soucient pas de la vitesse, des optimisations et de toutes les fonctionnalités HTML5 modernes. Les fermetures ne coûteront donc rien.
Gabriel Llamas
6
Il est désormais de 0,5% (août 2012) w3schools.com/browsers/browsers_explorer.asp
Lorenzo Polidori
7
@LorenzoPolidori w3schools users! == corporate web users;]
WynandB
Une convention de dénomination (ex: ajouter un trait de soulignement) est la voie à suivre. Le code est plus facile à maintenir et les méthodes sont toujours définies sur prototype. De nos jours cependant ... je marque juste la méthode comme privée en tapuscrit.
David Sherret
5

Ne soyez pas si bavard. C'est Javascript. Utilisez une convention de dénomination .

Après des années de travail dans les classes es6, j'ai récemment commencé à travailler sur un projet es5 (en utilisant requireJS qui est déjà très bavard). J'ai été à plusieurs reprises sur toutes les stratégies mentionnées ici et tout se résume à utiliser une convention de dénomination :

  1. Javascript n'a pas de mots clés de portée comme private. Les autres développeurs saisissant Javascript le sauront d'emblée. Par conséquent, une simple convention de dénomination est plus que suffisante. Une simple convention de dénomination consistant à préfixer un trait de soulignement résout le problème des propriétés privées et des méthodes privées.
  2. Profitons du Prototype pour des raisons de vitesse, mais ne soyons pas plus verbeux que ça. Essayons de garder la "classe" es5 aussi proche de ce à quoi nous pouvons nous attendre dans d'autres langages backend (et traitons chaque fichier comme une classe, même si nous n'avons pas besoin de retourner une instance).
  3. Démontrons avec une situation de module plus réaliste (nous utiliserons les anciens es5 et les vieux requireJs).

my-tooltip.js

    define([
        'tooltip'
    ],
    function(
        tooltip
    ){

        function MyTooltip() {
            // Later, if needed, we can remove the underscore on some
            // of these (make public) and allow clients of our class
            // to set them.
            this._selector = "#my-tooltip"
            this._template = 'Hello from inside my tooltip!';
            this._initTooltip();
        }

        MyTooltip.prototype = {
            constructor: MyTooltip,

            _initTooltip: function () {
                new tooltip.tooltip(this._selector, {
                    content: this._template,
                    closeOnClick: true,
                    closeButton: true
                });
            }
        }

        return {
            init: function init() {
               new MyTooltip();  // <-- Our constructor adds our tooltip to the DOM so not much we need to do after instantiation.
            }

            // You could instead return a new instantiation, 
            // if later you do more with this class.
            /* 
            create: function create() {
               return new MyTooltip();
            }
            */
        }
    });
prograhammer
la source
2
Il convient de noter que ni le langage Javascript ni aucun hôte de navigateur typique ne définissent des objets qui s'appuient sur des conventions de dénomination pour «masquer» l'état privé, de sorte que, même si vous avez raison, les développeurs saisiront le concept, cela conduit toujours à Approche OO de la programmation OO.
remer riche
Puis-je demander une bonne référence pour faire cela? Il y a des parties sur cet exemple qui sont nouvelles pour moi. Le define, et constructoret la structure elle-même. Bien que je sois principalement d'accord sur la réponse, j'ai commencé à travailler avec JS avec beaucoup d'influence OOP et j'ai même sauté trop tôt vers TS depuis que j'avais une expérience préalable en C #. Je pense que je dois désapprendre ces trucs et comprendre le paradigme de prototypage / procédure. (upvoted, btw)
Cold Cerberus
1
@ColdCerberus cet extrait de code utilise es5. Vous pouvez voir une image complète de cette approche ici: gist.github.com/jonnyreeves/2474026 . Mais gardez à l'esprit, vous voudrez adopter cette approche et la mettre à jour en utilisant les classes es6 : modules googlechrome.github.io/samples/classes-es6 et es6 (syntaxe d'importation / exportation): hackernoon.com/…
prograhammer
5

Vous pouvez le faire maintenant avec les méthodes privées es10 . Vous avez juste besoin d'ajouter un #avant le nom de la méthode.

class ClassWithPrivateMethod {
  #privateMethod() {
    return 'hello world';
  }

  getPrivateMessage() {
    return #privateMethod();
  }
}
D-Marc
la source
2
Sauf qu'il s'agit de l'étape 3 et ne fait pas encore officiellement partie de la langue.
misterhtmlcss
3

Prenez l'une des solutions qui suivent le modèle privé ou privilégié de Crockford . Par exemple:

function Foo(x) {
    var y = 5;
    var bar = function() {
        return y * x;
    };

    this.public = function(z) {
        return bar() + x * z;
    };
}

Dans tous les cas où l'attaquant n'a aucun droit "exécuter" sur le contexte JS, il n'a aucun moyen d'accéder à des champs ou méthodes "publics" ou "privés". Dans le cas où l'attaquant a cet accès, il peut exécuter ce one-liner:

eval("Foo = " + Foo.toString().replace(
    /{/, "{ this.eval = function(code) { return eval(code); }; "
));

Notez que le code ci-dessus est générique pour tout constructeur-type-confidentialité. Cela échouera avec certaines des solutions ici, mais il devrait être clair que presque toutes les solutions basées sur la fermeture peuvent être cassées comme ceci avec différents replace()paramètres.

Après cela, tout objet créé avec new Foo()aura une evalméthode qui peut être appelée pour retourner ou modifier des valeurs ou des méthodes définies dans la fermeture du constructeur, par exemple:

f = new Foo(99);
f.eval("x");
f.eval("y");
f.eval("x = 8");

Le seul problème que je peux voir avec cela est que cela ne fonctionnera pas dans les cas où il n'y a qu'une seule instance et qu'il est créé lors du chargement. Mais alors il n'y a aucune raison de définir réellement un prototype et dans ce cas, l'attaquant peut simplement recréer l'objet au lieu du constructeur tant qu'il a un moyen de passer les mêmes paramètres (par exemple, ils sont constants ou calculés à partir des valeurs disponibles).

À mon avis, cela rend à peu près la solution de Crockford inutile. Étant donné que la "confidentialité" est facilement brisée, les inconvénients de sa solution (lisibilité et maintenabilité réduites, performances réduites, mémoire accrue) font de la méthode basée sur le prototype "sans confidentialité" le meilleur choix.

J'utilise généralement des traits de soulignement principaux pour marquer __privateet les _protectedméthodes et les champs (style Perl), mais l'idée d'avoir la confidentialité en JavaScript montre simplement comment c'est un langage mal compris.

Je ne suis donc pas d'accord avec Crockford, à l' exception de sa première phrase.

Alors, comment obtenez-vous une réelle confidentialité dans JS? Mettez tout ce qui est nécessaire pour être privé côté serveur et utilisez JS pour faire des appels AJAX.

Fozi
la source
Il s'agit d'un problème grave qui devrait être mieux connu. Existe-t-il une «défense» contre cette attaque?
James
@James Aucun que je sache, je pense que c'est la nature de la bête. Comme je l'ai souligné, vous pouvez déplacer des fonctionnalités vers le serveur où elles s'exécutent dans un environnement protégé. Le point que je voulais aborder dans ma réponse est que la solution de Crockford n'aide pas, complique inutilement le code et cache la nécessité de faire quelque chose.
Fozi
Si un utilisateur entre un mot de passe secret, il ne peut pas le faire côté serveur. À un moment donné, le mot de passe sera dans une variable «privée». Un attaquant pourrait-il donc le lire? Je fais confiance à mon code et de toute façon les normes de ma maison ne permettent pas eval (). L'attaquant pourrait être un plug-in ou une bibliothèque JavaScript malveillant que je n'ai pas vérifié correctement - donc, oui, je dois les vérifier. L'attaquant pourrait également être quelque chose comme une annonce sur le côté qui ne devrait pas interagir avec mon code. Je me protège contre cela en enveloppant tout mon code dans un anonyme (function () {allMyStuff}());afin de ne rien exposer de global.
James
@James Cela devient OT, si vous voulez continuer, veuillez ouvrir une nouvelle question. Oui, un attaquant peut lire le mot de passe. De votre variable "privée". Ou depuis le DOM. Ou il peut remplacer l'API AJAX. Ou il remplace votre page par autre chose. S'il ne peut faire aucune des opérations ci-dessus, il n'y a pas besoin de "confidentialité" de JS car il ne peut pas non plus lire vos variables JS. Le fait est que la «solution» de Crockford que tout le monde utilise actuellement n'aide pas à résoudre ce problème.
Fozi
Je crois que l'obfuscation de code pseudo-aléatoire peut être une défense faible contre cette attaque - plus difficile de modifier le corps de la fonction lorsque vous ne pouvez pas dépendre de la fonction ayant un nom fixe; plus difficile à faire f.eval('nameOfVariable')quand on ne sait pas ce que 'nameOfVariable'c'est ...
Gershom
2

Si vous souhaitez que la gamme complète des fonctions publiques et privées avec la possibilité pour les fonctions publiques d'accéder aux fonctions privées, mettez en forme le code d'un objet comme celui-ci:

function MyObject(arg1, arg2, ...) {
  //constructor code using constructor arguments...
  //create/access public variables as 
  // this.var1 = foo;

  //private variables

  var v1;
  var v2;

  //private functions
  function privateOne() {
  }

  function privateTwon() {
  }

  //public functions

  MyObject.prototype.publicOne = function () {
  };

  MyObject.prototype.publicTwo = function () {
  };
}
domgblackwell
la source
Quelqu'un peut-il me dire pourquoi cela a été rejeté? Cela me semble correct.
thomasrutter
10
Chaque fois que vous effectuez un new MyObject, le prototype de MyObjectest remplacé par les mêmes valeurs.
bpierre
2
-1. N'attribuez jamais à l' .prototypeintérieur le constructeur.
Bergi
2
var TestClass = function( ) {

    var privateProperty = 42;

    function privateMethod( ) {
        alert( "privateMethod, " + privateProperty );
    }

    this.public = {
        constructor: TestClass,

        publicProperty: 88,
        publicMethod: function( ) {
            alert( "publicMethod" );
            privateMethod( );
        }
    };
};
TestClass.prototype = new TestClass( ).public;


var myTestClass = new TestClass( );

alert( myTestClass.publicProperty );
myTestClass.publicMethod( );

alert( myTestClass.privateMethod || "no privateMethod" );

Similaire à georgebrock mais un peu moins verbeux (IMHO) Des problèmes pour le faire de cette façon? (Je ne l'ai vu nulle part)

edit: J'ai réalisé que c'était un peu inutile car chaque instanciation indépendante a sa propre copie des méthodes publiques, ce qui compromet l'utilisation du prototype.

David
la source
2

Voici ce que j'ai le plus apprécié jusqu'à présent en ce qui concerne les méthodes / membres privés / publics et l'instanciation en javascript:

voici l'article: http://www.sefol.com/?p=1090

et voici l'exemple:

var Person = (function () {

    //Immediately returns an anonymous function which builds our modules 
    return function (name, location) {

        alert("createPerson called with " + name);

        var localPrivateVar = name;

        var localPublicVar = "A public variable";

        var localPublicFunction = function () {
            alert("PUBLIC Func called, private var is :" + localPrivateVar)
        };

        var localPrivateFunction = function () {
            alert("PRIVATE Func called ")
        };

        var setName = function (name) {

            localPrivateVar = name;

        }

        return {

            publicVar: localPublicVar,

            location: location,

            publicFunction: localPublicFunction,

            setName: setName

        }

    }
})();


//Request a Person instance - should print "createPerson called with ben"
var x = Person("ben", "germany");

//Request a Person instance - should print "createPerson called with candide"
var y = Person("candide", "belgium");

//Prints "ben"
x.publicFunction();

//Prints "candide"
y.publicFunction();

//Now call a public function which sets the value of a private variable in the x instance
x.setName("Ben 2");

//Shouldn't have changed this : prints "candide"
y.publicFunction();

//Should have changed this : prints "Ben 2"
x.publicFunction();

JSFiddle: http://jsfiddle.net/northkildonan/kopj3dt3/1/

low_rents
la source
cette approche a une préoccupation importante - si vous avez créé 2 objets, en mémoire il y aura 2 mêmes méthds (PublicFunction par exemple) 1000 objets mangeront toute votre mémoire.
Artem G
2

Le modèle de module est correct dans la plupart des cas. Mais si vous avez des milliers d'instances, les classes économisent de la mémoire. Si la sauvegarde de la mémoire est un problème et que vos objets contiennent une petite quantité de données privées, mais ont de nombreuses fonctions publiques, vous souhaiterez que toutes les fonctions publiques vivent dans le .prototype pour économiser de la mémoire.

Voici ce que j'ai trouvé:

var MyClass = (function () {
    var secret = {}; // You can only getPriv() if you know this
    function MyClass() {
        var that = this, priv = {
            foo: 0 // ... and other private values
        };
        that.getPriv = function (proof) {
            return (proof === secret) && priv;
        };
    }
    MyClass.prototype.inc = function () {
        var priv = this.getPriv(secret);
        priv.foo += 1;
        return priv.foo;
    };
    return MyClass;
}());
var x = new MyClass();
x.inc(); // 1
x.inc(); // 2

L'objet privcontient des propriétés privées. Il est accessible via la fonction publique getPriv(), mais cette fonction retourne falsesauf si vous la passez secret, et cela n'est connu qu'à l'intérieur de la fermeture principale.

James
la source
Cela simule les membres protégés, les types qui en héritent peuvent également accéder aux membres protégés. Je préfère également ce modèle au privé
HMR
2

Et ça?

var Restaurant = (function() {

 var _id = 0;
 var privateVars = [];

 function Restaurant(name) {
     this.id = ++_id;
     this.name = name;
     privateVars[this.id] = {
         cooked: []
     };
 }

 Restaurant.prototype.cook = function (food) {
     privateVars[this.id].cooked.push(food);
 }

 return Restaurant;

})();

La recherche de variable privée est impossible en dehors de la portée de la fonction immédiate. Il n'y a pas de duplication de fonctions, économisant de la mémoire.

L'inconvénient est que la recherche de variables privées est maladroite privateVars[this.id].cookedest ridicule à taper. Il existe également une variable "id" supplémentaire.

Evan Leis
la source
Cela restera Restaurantcomme undefinedparce que vous ne renvoyez rien de la fonction anonyme.
user4815162342
Ou et comment? En supposant que la référence au restaurant créé est perdue, privateVars n'aura pas de référence à son propriétaire. Le graphique de référence est acyclique. Qu'est-ce que je rate?
Evan Leis
En fait, c'est la seule réponse qui prend en charge les propriétés privées en plus des méthodes. Les deux seuls problèmes sont déjà mentionnés dans la réponse.
pishpish
Je vois une fuite de mémoire: lorsqu'une instance de Restauranta été récupérée, ses valeurs restent à l'intérieur privateVars. Un WeakMappeut être un bon remplacement pour le Arraydans ce cas.
Gershom
2

Enveloppez tout le code dans une fonction anonyme: Ensuite, toutes les fonctions seront privées, UNIQUEMENT les fonctions attachées à l' windowobjet:

(function(w,nameSpacePrivate){
     w.Person=function(name){
         this.name=name;   
         return this;
     };

     w.Person.prototype.profilePublic=function(){
          return nameSpacePrivate.profile.call(this);
     };  

     nameSpacePrivate.profile=function(){
       return 'My name is '+this.name;
     };

})(window,{});

Utilisez ceci :

  var abdennour=new Person('Abdennour');
  abdennour.profilePublic();

VIOLON

Abdennour TOUMI
la source
1

Je préfère stocker des données privées dans un associé WeakMap. Cela vous permet de conserver vos méthodes publiques sur le prototype auquel elles appartiennent. Cela semble être le moyen le plus efficace de gérer ce problème pour un grand nombre d'objets.

const data = new WeakMap();

function Foo(value) {
    data.set(this, {value});
}

// public method accessing private value
Foo.prototype.accessValue = function() {
    return data.get(this).value;
}

// private 'method' accessing private value
function accessValue(foo) {
    return data.get(foo).value;
}

export {Foo};
riche souvenir
la source
0

Les fonctions privées ne peuvent pas accéder aux variables publiques à l'aide du modèle de module

Dooma
la source
0

Puisque tout le monde postait ici son propre code, je vais le faire aussi ...

J'aime Crockford parce qu'il a introduit de vrais modèles orientés objet en Javascript. Mais il a également trouvé un nouveau malentendu, celui-là.

Alors pourquoi utilise-t-il "that = this"? Cela n'a rien à voir avec des fonctions privées. Cela a à voir avec les fonctions internes!

Parce que selon Crockford, c'est du code buggé:

Function Foo( ) {
    this.bar = 0; 
    var foobar=function( ) {
        alert(this.bar);
    }
} 

Il a donc suggéré de faire ceci:

Function Foo( ) {
    this.bar = 0;
    that = this; 
    var foobar=function( ) {
        alert(that.bar);
    }
}

Donc, comme je l'ai dit, je suis sûr que Crockford s'est trompé sur son explication à ce sujet et cela (mais son code est certainement correct). Ou trompait-il simplement le monde Javascript, pour savoir qui copie son code? Je ne sais pas ... Je ne suis pas un geek du navigateur; D

ÉDITER

Ah, c'est de cela qu'il s'agit: qu'est-ce que 'var that = this;' dire en JavaScript?

Donc Crockie avait vraiment tort avec son explication ... mais avec son code, donc c'est toujours un bon gars. :))

marque
la source
0

En général, j'ai ajouté temporairement l'objet privé _ à l'objet. Vous devez ouvrir la confidentialité exlipcitly dans le "Power-constructor" pour la méthode. Si vous appelez la méthode à partir du prototype, vous pourrez remplacer la méthode-prototype

  • Rendre une méthode publique accessible dans le "Power-constructor": (ctx est le contexte de l'objet)

    ctx.test = GD.Fabric.open('test', GD.Test.prototype, ctx, _); // is a private object
  • Maintenant, j'ai cette openPrivacy:

    GD.Fabric.openPrivacy = function(func, clss, ctx, _) {
        return function() {
            ctx._ = _;
            var res = clss[func].apply(ctx, arguments);
            ctx._ = null;
            return res;
        };
    };
Andreas Dyballa
la source
0

Voici ce que j'ai calculé:

Besoin d'une classe de code sucre que vous pouvez trouver ici . Prend également en charge les éléments protégés, d'héritage, virtuels, statiques ...

;( function class_Restaurant( namespace )
{
    'use strict';

    if( namespace[ "Restaurant" ] ) return    // protect against double inclusions

        namespace.Restaurant = Restaurant
    var Static               = TidBits.OoJs.setupClass( namespace, "Restaurant" )


    // constructor
    //
    function Restaurant()
    {
        this.toilets = 3

        this.Private( private_stuff )

        return this.Public( buy_food, use_restroom )
    }

    function private_stuff(){ console.log( "There are", this.toilets, "toilets available") }

    function buy_food     (){ return "food"        }
    function use_restroom (){ this.private_stuff() }

})( window )


var chinese = new Restaurant

console.log( chinese.buy_food()      );  // output: food
console.log( chinese.use_restroom()  );  // output: There are 3 toilets available
console.log( chinese.toilets         );  // output: undefined
console.log( chinese.private_stuff() );  // output: undefined

// and throws: TypeError: Object #<Restaurant> has no method 'private_stuff'

la source
0
Class({  
    Namespace:ABC,  
    Name:"ClassL2",  
    Bases:[ABC.ClassTop],  
    Private:{  
        m_var:2  
    },  
    Protected:{  
        proval:2,  
        fight:Property(function(){  
            this.m_var--;  
            console.log("ClassL2::fight (m_var)" +this.m_var);  
        },[Property.Type.Virtual])  
    },  
    Public:{  
        Fight:function(){  
            console.log("ClassL2::Fight (m_var)"+this.m_var);  
            this.fight();  
        }  
    }  
});  

https://github.com/nooning/JSClass

snowkid
la source
0

J'ai créé un nouvel outil pour vous permettre d'avoir de vraies méthodes privées sur le prototype https://github.com/TremayneChrist/ProtectJS

Exemple:

var MyObject = (function () {

  // Create the object
  function MyObject() {}

  // Add methods to the prototype
  MyObject.prototype = {

    // This is our public method
    public: function () {
      console.log('PUBLIC method has been called');
    },

    // This is our private method, using (_)
    _private: function () {
      console.log('PRIVATE method has been called');
    }
  }

  return protect(MyObject);

})();

// Create an instance of the object
var mo = new MyObject();

// Call its methods
mo.public(); // Pass
mo._private(); // Fail
Trem
la source
1
Pouvez-vous expliquer comment cela fonctionne, s'il vous plaît? Comment / où pouvez- vous appeler la _privateméthode?
Bergi
0

Vous devez mettre une fermeture autour de votre fonction constructeur réelle, où vous pouvez définir vos méthodes privées. Pour modifier les données des instances via ces méthodes privées, vous devez leur donner "this" avec eux, soit en tant qu'argument de fonction, soit en appelant cette fonction avec .apply (this):

var Restaurant = (function(){
    var private_buy_food = function(that){
        that.data.soldFood = true;
    }
    var private_take_a_shit = function(){
        this.data.isdirty = true;   
    }
    // New Closure
    function restaurant()
    {
        this.data = {
            isdirty : false,
            soldFood: false,
        };
    }

    restaurant.prototype.buy_food = function()
    {
       private_buy_food(this);
    }
    restaurant.prototype.use_restroom = function()
    {
       private_take_a_shit.call(this);
    }
    return restaurant;
})()

// TEST:

var McDonalds = new Restaurant();
McDonalds.buy_food();
McDonalds.use_restroom();
console.log(McDonalds);
console.log(McDonalds.__proto__);
Flex Elektro Deimling
la source
En fait, ça ne marche pas. Chacun new Restaurantaura son propre constructeur restaurantet le "prototype" est totalement abusé.
Bergi
@Bergi. En fait, vous avez raison. Ça marcherait mais ce serait aussi une ressource porcine (ça s'appelle comme ça?). J'ai édité ma réponse à ce sujet.
Flex Elektro Deimling
Merci pour la mise à jour. Aucune idée de comment appeler la version précédente (mais "bug" :-)
Bergi
0

Je sais qu'il est un peu trop tard mais qu'en est-il?

var obj = function(){
    var pr = "private";
    var prt = Object.getPrototypeOf(this);
    if(!prt.hasOwnProperty("showPrivate")){
        prt.showPrivate = function(){
            console.log(pr);
        }
    }    
}

var i = new obj();
i.showPrivate();
console.log(i.hasOwnProperty("pr"));
Maxim Balaganskiy
la source
0

Il y a déjà beaucoup de réponses à cette question, mais rien ne correspondait à mes besoins. J'ai donc trouvé ma propre solution, j'espère qu'elle est utile pour quelqu'un:

function calledPrivate(){
    var stack = new Error().stack.toString().split("\n");
    function getClass(line){
        var i = line.indexOf(" ");
        var i2 = line.indexOf(".");
        return line.substring(i,i2);
    }
    return getClass(stack[2])==getClass(stack[3]);
}

class Obj{
    privateMethode(){
        if(calledPrivate()){
            console.log("your code goes here");
        }
    }
    publicMethode(){
        this.privateMethode();
    }
}

var obj = new Obj();
obj.publicMethode(); //logs "your code goes here"
obj.privateMethode(); //does nothing

Comme vous pouvez le voir, ce système fonctionne lors de l'utilisation de ce type de classes en javascript. Pour autant que je sache, aucune des méthodes commentées ci-dessus ne l'a fait.

thegunmaster
la source
1
Curieux: votre besoin était-il vraiment d'exposer la fonction, mais d'en faire un no-op en exécution - plutôt que de la cacher aux appelants externes comme le font toutes / la plupart des autres réponses? Si oui, pourquoi? Quels sont, selon vous, les avantages de cette approche? Pour moi, cela semble être juste un surcoût de performance inutile, une API peu claire et, bien, probablement lié à un enfer de débogage, mais je suis toujours ouvert à de nouvelles perspectives ...
JHH
2
@JHH pour être honnête, je suis à peu près palpitant face à cela. Les frais généraux ne valent généralement pas la peine du tout, bien que pour moi cela n'ait pas beaucoup d'importance car je n'ai pas fait beaucoup d'appels à ces cours. La raison pour laquelle je l'ai fait de cette façon est simplement qu'il est relativement propre dans la façon dont vous écrivez et appelez les fonctions. Je ne comprenais pas les symboles et autres à l'époque, mais maintenant que je le sais, je pense que c'est généralement la voie à suivre lorsque j'utilise des cours. J'envisage de supprimer cette réponse tous ensemble. J'ai posté plusieurs réponses stupides, mais bon, vous vivez et apprenez.
thegunmaster
Merci pour les commentaires! Je ne savais pas si j'avais mal compris quelque chose. Mais oui, nous vivons et apprenons tous!
JHH
0

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

kofifus
la source