Metodi privati ​​JavaScript


482

Per creare una classe JavaScript con un metodo pubblico farei qualcosa del tipo:

function Restaurant() {}

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

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

In questo modo gli utenti della mia classe possono:

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

Come faccio a creare un metodo privato che può essere chiamato dai metodi buy_foode use_restroomma non esternamente dagli utenti della classe?

In altre parole, voglio che la mia implementazione del metodo sia in grado di fare:

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

Ma questo non dovrebbe funzionare:

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

Come definisco private_stuffun metodo privato in modo che entrambi siano veri?

Ho letto lo scritto di Doug Crockford alcune volte, ma non sembra che i metodi "privati" possano essere chiamati con metodi pubblici e che i metodi "privilegiati" possano essere chiamati esternamente.

Risposte:


403

Puoi farlo, ma il rovescio della medaglia è che non può far parte del prototipo:

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

9
Nascondere i sottili all'interno della chiusura non garantirà la privacy di tutti gli interpreti. Vedi code.google.com/p/google-caja/wiki/…
Mike Samuel,

51
@mikesamuel - vero, ma solo quando quegli interpreti hanno dei bug :)
jvenema,

133
Questo è un metodo privato, ma tenderà a consumare molta più memoria rispetto a un normale metodo prototipo, soprattutto se stai creando molti di questi oggetti. Per ogni istanza di oggetto, crea una funzione separata associata all'oggetto e non alla classe. Inoltre, questo non ottiene la spazzatura raccolta fino a quando l'oggetto stesso non viene distrutto.
Arindam,

4
Se fai un oggetto ereditato da McDonalds () da Restaurant (), McDonalds non può ignorare i metodi privati ​​se li dichiari in questo modo. Bene, in realtà puoi, ma non funzionerà se qualche altro metodo chiama il privato, chiamerà la versione originale del metodo e non puoi nemmeno chiamare il metodo genitore. Finora non ho trovato un buon modo per dichiarare metodi privati ​​che funzionano bene con l'ereditarietà. Questo e le implicazioni sulle prestazioni non lo rendono affatto un ottimo modello di progettazione. Consiglierei di fare una sorta di prefisso per indicare metodi privati, come se fosse un sottolineato.
Hoffmann,

68
I metodi privati ​​non dovrebbero essere sostituiti, sono privati.
17 del 26

163

Utilizzo della funzione e della chiamata auto invocanti

JavaScript utilizza prototipi e non ha classi (o metodi a tale proposito) come i linguaggi orientati agli oggetti. Uno sviluppatore JavaScript deve pensare in JavaScript.

Citazione di Wikipedia:

A differenza di molti linguaggi orientati agli oggetti, non esiste alcuna distinzione tra una definizione di funzione e una definizione di metodo. Piuttosto, la distinzione si verifica durante la chiamata di funzione; quando una funzione viene chiamata come metodo di un oggetto, la funzione locale questa parola chiave viene associata a quell'oggetto per quell'invocazione.

Soluzione che utilizza una funzione auto-invocante e la funzione call per chiamare il "metodo" privato:

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 funzione di chiamata ci consente di chiamare la funzione privata con il contesto appropriato ( this).


Più semplice con Node.js

Se stai usando node.js , non hai bisogno di IIFE perché puoi sfruttare il sistema di caricamento del modulo :

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

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

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

exports.MyObject = MyObject;

Carica il file:

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

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


(sperimentale) ES7 con Bind Operator

L'operatore di bind ::è una proposta ECMAScript ed è implementato in Babel ( fase 0 ).

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

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

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

Carica il file:

import MyObject from './MyObject';

let myObject = new MyObject('bar');
myObject.publicFun();      // Returns '>>bar'
myObject.privateFun('>>'); // TypeError: myObject.privateFun is not a function

34
Questa è la risposta migliore I vantaggi dei metodi privati, nessuna spazzatura.
TaylorMac,

1
@TaylorMac Tranne la .callparte.
Pishpish,

1
@janje Huh? Questo è il punto della domanda, non c'è privato f()in JavaScript (non al momento).
Yves M.

2
@YvesM. Il punto della domanda è scegliere il modello migliore che lo simula.
Pishpish,

1
@cambiato qual è il miglior compromesso per avere funzioni nascoste (non richiamabili dall'esterno), bella sintassi (no .call) e poca memoria (nessuna funzione sull'istanza )? Esiste anche?
Ciprian Tomoiagă,

161

Puoi simulare metodi privati ​​come questo:

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

Maggiori informazioni su questa tecnica qui: http://webreflection.blogspot.com/2008/04/natural-javascript-private-methods.html


7
Vorrei anche suggerire il sito di Douglas Crockford come risorsa su metodi e membri privati ​​/ pubblici javascript.crockford.com/private.html
Jared

10
Ha menzionato quel link nella domanda.
Gulzar Nazim,

8
L'aspetto negativo di questo metodo è che non puoi avere private_stuff () accedere ad altri dati privati ​​in Restaurant e altri metodi di Restaurant non possono chiamare private_stuff (). Il lato positivo è che se non hai bisogno di nessuna delle condizioni che ho appena menzionato, puoi mantenere use_restroom () nel prototipo.
17 del 26

6
Questa dovrebbe essere la soluzione e la risposta accettata perché l'autore sta chiaramente usando la proprietà prototipo.
Gabriel Llamas,

23
Con lo schema proposto da @georgebrock, tutti i dati privati ​​saranno condivisi tra tutti gli oggetti ristorante. È simile a variabili e funzioni private statiche in OOP basato sulla classe. Presumo che l'OP non lo voglia. Per illustrare cosa intendo, ho creato un jsFiddle .
febbraio

35

In queste situazioni, quando si dispone di un'API pubblica e si desidera metodi / proprietà privati ​​e pubblici, utilizzo sempre il modello del modulo. Questo modello è stato reso popolare all'interno della libreria YUI e i dettagli sono disponibili qui:

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

È davvero semplice e facile da comprendere per gli altri sviluppatori. Per un semplice esempio:

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

questo genere di cose non verrebbe rilevato dal completamento automatico di un IDE :(
Fai clic su Aggiorna

19
Ma questa non è una classe, quindi non puoi avere 2 "istanze" di questo con stati diversi.
DevAntoine,

rimuovi la parte () e hai una "classe". almeno dove è possibile creare istanze di istanze diverse con stati diversi. lo schema del modulo è piuttosto impegnativo per la memoria, però ...
oligofren,

@DevAntoine Guarda i commenti per la risposta di 17 su 26. In JavaScript le classi estensibili e i metodi privati ​​non vanno facilmente di pari passo. Il mio suggerimento in questo caso sarebbe quello di andare con la composizione sull'eredità. Crea un prototipo estensibile con gli stessi metodi dell'oggetto in calcestruzzo racchiuso. Quindi interno al tuo prototipo puoi decidere quando chiamare i metodi sul tuo oggetto concreto.

C'è qualche svantaggio nel chiamare le variabili pubbliche dalle funzioni private in questo modo aPrivateMethod = function() { MYLIB.aPublicProperty}:?
Hanna,

21

Ecco la classe che ho creato per capire cosa ha suggerito Douglas Crockford nel suo sito 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

}

5
Quello = questo è uno schema abbastanza comune, reso popolare dal suddetto Crockford nel suo libro "Javascript: le parti buone"
oligofren,

8
thatviene utilizzato invece di thisevitare problemi di scoping, quando le funzioni sono associate a un oggetto diverso. Qui, si memorizzano thisin thate mai utilizzato di nuovo, che è la stessa di non farlo affatto. Dovresti cambiare thiscon thattutte le Constructorfunzioni interne (non la dichiarazione dei metodi). Se employeeè applyed o called in qualche modo questi metodi potrebbero essere lanciati poiché thissaranno erroneamente associati.
Maroshii,

Inoltre, ogni istanza avrà una copia completa delle funzioni private, inefficiente. A parte il fatto che i metodi pubblici non possono accedere a classi private, mi fa venire voglia di passare al dardo. Purtroppo angulardart è super beta.
Ray Foss,

Dov'è il metodo "costruttore" in questo? Dove metterei la logica che verrebbe normalmente eseguita nel metodo di costruzione di una classe quando viene istanziata?
BadHorsie

13

Ho evocato questo: EDIT: In realtà, qualcuno ha collegato a una soluzione identica. 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!'

1
Questa è una bella tecnica, ma come permetteresti i parametri nel tuo costruttore? Ad esempio var getAwayVehicle = new Car(100);dove si 100trova la velocità e si desidera avvisare la velocità. Grazie!
Jason,

1
Capito, può avere var Car = function(speed) { this.speed = speed; }e "return {costruttore: Auto, ..."
Jason

11

Penso che tali domande sorgano ancora e ancora a causa della mancanza di comprensione delle chiusure. Le chiusure sono la cosa più importante in JS. Ogni programmatore JS deve sentirne l'essenza.

1. Prima di tutto dobbiamo creare un ambito separato (chiusura).

function () {

}

2. In quest'area, possiamo fare quello che vogliamo. E nessuno lo saprà.

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. Perché il mondo sia a conoscenza della nostra lezione di ristorazione, dobbiamo restituirla dalla chiusura.

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

4. Alla fine, abbiamo:

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. Inoltre, questo approccio ha il potenziale per l'ereditarietà e il templating

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

Spero che questo aiuti qualcuno a capire meglio questo argomento


2
Quello che hai al punto 4. è meraviglioso! Penso che sia l'unica risposta da tutti quelli qui in cui si ottengono sia i guadagni di prestazioni / memoria dell'uso dei metodi sul prototipo E questi metodi pubblici hanno pieno accesso ai membri privati. +1
Hudon

7
Non funziona La variabile nome qui funziona come variabile statica ed è condivisa da tutte le istanze di Restaurant. Ecco jsbin: jsbin.com/oqewUWa/2/edit?js,output
Shital Shah

è una buona prova, ma come ha sottolineato Shital, la variabile del nome è errata.
Oligofren,

2
aggiungendo il mio 2c qui per riaffermare che non funziona, sembra carino, ma come sottolineato sopra "name" servirà come variabile statica, cioè condivisa in tutti i casi
Paul Carroll,

10

Personalmente, preferisco il seguente modello per la creazione di classi in 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(); }
        ...
    };
})();

Come puoi vedere, ti permette di definire sia le proprietà di classe sia le proprietà di istanza, ognuna delle quali può essere pubblica e privata.


dimostrazione

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

Vedi anche questo violino .


Questo mi fa venire voglia di usare le classi es6 e vedere cosa traspila anche
sheriffderek

9

Tutta questa chiusura ti costerà. Assicurati di testare le implicazioni sulla velocità soprattutto in IE. Scoprirai che stai meglio con una convenzione di denominazione. Ci sono ancora molti utenti Web aziendali che sono costretti a utilizzare IE6 ...


34
A chi importa, sul serio?
nowayyy,

17
Quel 9% che sta ancora utilizzando IE6 non si preoccupa della velocità, delle ottimizzazioni e di tutte le moderne funzionalità HTML5. Quindi le chiusure non costeranno nulla.
Gabriel Llamas,


7
@LorenzoPolidori w3schools users! == corporate web users;]
WynandB

Una convenzione di denominazione (es: anteporre un carattere di sottolineatura) è la strada da percorrere. Il codice è più facile da mantenere e i metodi sono ancora definiti sul prototipo. Al giorno d'oggi, però ... Contrassegno il metodo come privato in dattiloscritto.
David Sherret,

5

Non essere così prolisso. È Javascript. Usa una convenzione di denominazione .

Dopo anni di lavoro in classi es6, di recente ho iniziato a lavorare su un progetto es5 (usando RequireS che è già molto dettagliato). Sono stato ripetutamente tutte le strategie menzionate qui e praticamente tutto si riduce a usare una convenzione di denominazione :

  1. Javascript non ha parole chiave dell'ambito come private. Altri sviluppatori che accedono a Javascript lo sapranno in anticipo. Pertanto, una semplice convenzione di denominazione è più che sufficiente. Una semplice convenzione di denominazione del prefisso con un trattino basso risolve il problema sia delle proprietà private che dei metodi privati.
  2. Approfittiamo del prototipo per motivi di velocità, ma non lasciamoci più prolisso. Proviamo a mantenere la "classe" es5 molto simile a ciò che potremmo aspettarci in altre lingue di backend (e trattiamo ogni file come una classe, anche se non è necessario restituire un'istanza).
  3. Dimostriamo con una situazione di modulo più realistica (useremo i vecchi es5 e i vecchi requestJs).

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

2
Va notato che né il linguaggio Javascript né alcun host del browser tipico definiscono oggetti che si basano su convenzioni di denominazione per "nascondere" lo stato privato, quindi mentre potresti avere ragione che gli sviluppatori afferreranno il concetto, ciò porta comunque a un Approccio OO alla programmazione OO.
rich remer

Posso chiedere un buon riferimento nel fare questo? Ci sono parti di questo esempio che sono nuove per me. Il define, ee constructorla struttura stessa. Anche se concordo principalmente sulla risposta, ho iniziato a lavorare con JS con molta influenza OOP e sono persino passato troppo presto a TS poiché avevo avuto precedenti esperienze con C #. Penso di dover disimparare queste cose e capire il prototipo / paradigma procedurale. (votato, a proposito)
Cold Cerberus,

1
@ColdCerberus questo frammento di codice utilizza es5. Puoi vedere un quadro completo di questo approccio qui: gist.github.com/jonnyreeves/2474026 . Ma tieni presente che vorrai adottare questo approccio e aggiornarlo utilizzando le classi es6 : googlechrome.github.io/samples/classes-es6 ed moduli es6 (sintassi import / export): hackernoon.com/…
prograhammer

5

Puoi farlo ora con i metodi privati ​​es10 . Devi solo aggiungere un #prima del nome del metodo.

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

  getPrivateMessage() {
    return #privateMethod();
  }
}

2
Tranne che questa è la fase 3 e non è ancora ufficialmente parte della lingua.
misterhtmlcss,

3

Prendi una delle soluzioni che seguono il modello privato o privilegiato di Crockford . Per esempio:

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

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

In ogni caso in cui l'attaccante non ha diritto di "esecuzione" sul contesto JS, non ha modo di accedere a campi o metodi "pubblici" o "privati". Nel caso in cui l'attaccante abbia tale accesso, può eseguire questo one-liner:

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

Si noti che il codice sopra è generico per tutta la privacy di tipo costruttore. Fallirà con alcune delle soluzioni qui, ma dovrebbe essere chiaro che praticamente tutte le soluzioni basate sulla chiusura possono essere rotte in questo modo con replace()parametri diversi .

Dopo che questo è stato eseguito, qualsiasi oggetto creato con new Foo()avrà un evalmetodo che può essere chiamato per restituire o modificare valori o metodi definiti nella chiusura del costruttore, ad esempio:

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

L'unico problema che posso vedere con questo è che non funzionerà nei casi in cui esiste solo un'istanza ed è stata creata al caricamento. Ma poi non c'è motivo di definire effettivamente un prototipo e in tal caso l'attaccante può semplicemente ricreare l'oggetto invece del costruttore purché abbia un modo di passare gli stessi parametri (ad esempio, sono costanti o calcolati dai valori disponibili).

Secondo me, questo rende praticamente inutile la soluzione di Crockford. Dal momento che la "privacy" si rompe facilmente, gli aspetti negativi della sua soluzione (leggibilità e manutenibilità ridotte, prestazioni ridotte, memoria aumentata) rendono il metodo basato sul prototipo "nessuna privacy" la scelta migliore.

Io di solito uso di sottolineatura per contrassegnare leader __privatee _protectedmetodi e campi (stile Perl), ma l'idea di avere privacy in JavaScript solo mostra come si tratta di un linguaggio incompreso.

Pertanto non sono d'accordo con Crockford, fatta eccezione per la sua prima frase.

Quindi, come si ottiene la vera privacy in JS? Metti tutto ciò che è necessario per essere privato sul lato server e usa JS per fare chiamate AJAX.


Questo è un problema serio che dovrebbe essere più noto. Esiste una "difesa" contro questo attacco?
James,

@James Nessuno che io conosca, penso che sia la natura della bestia. Come ho sottolineato, è possibile spostare la funzionalità sul server in cui viene eseguita in un ambiente protetto. Il punto che volevo superare nella mia risposta è che la soluzione di Crockford non sta aiutando, complica inutilmente il codice e nasconde la necessità di fare qualcosa al riguardo.
Fozi,

Se un utente inserisce una password segreta, non può farlo sul lato server. Ad un certo punto la password sarà in una variante "privata". Quindi un utente malintenzionato potrebbe leggerlo? Mi fido del mio codice e comunque i miei standard di casa non consentono eval (). L'autore dell'attacco potrebbe essere un plug-in JavaScript o una libreria di terze parti dannosi che non ho verificato correttamente, quindi sì, devo verificarli. L'attaccante potrebbe anche essere qualcosa di simile a un annuncio pubblicitario sul lato che non dovrebbe interagire con il mio codice. Proteggo da ciò avvolgendo tutto il mio codice in un anonimo in (function () {allMyStuff}());modo da non rivelare nulla di globale.
James,

@James Questo sta diventando OT, se vuoi continuare, per favore apri una nuova domanda. Sì, un utente malintenzionato può leggere la password. Dalla tua variabile "privata". O dal DOM. Oppure può sostituire l'API AJAX. Oppure sostituisce la tua pagina con qualcos'altro. Se non è in grado di eseguire alcuna delle operazioni precedenti, non è necessaria la "privacy" di JS perché non è in grado di leggere nessuna delle variabili JS. Il punto è che la "soluzione" di Crockford che tutti stanno usando in questo momento non aiuta a risolvere questo problema.
Fozi,

Credo che l'offuscamento del codice pseudocasuale possa essere una difesa debole contro questo attacco - più difficile modificare il corpo della funzione quando non si può dipendere dalla funzione con un nome fisso; più difficile da fare f.eval('nameOfVariable')quando non sai cosa 'nameOfVariable'sia ...
Gershom il


2

Se si desidera l'intera gamma di funzioni pubbliche e private con la possibilità per le funzioni pubbliche di accedere a funzioni private, codice layout per un oggetto come questo:

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

Qualcuno può dirmi perché questo è stato votato verso il basso? Mi sembra buono.
thomasrutter,

10
Ogni volta che si esegue un new MyObject, il prototipo di MyObjectviene sostituito con gli stessi valori.
bpierre,

2
-1. Mai e poi mai assegnare .prototypeall'interno del costruttore.
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" );

Simile a georgebrock ma un po 'meno prolisso (IMHO) Qualche problema nel farlo in questo modo? (Non l'ho visto da nessuna parte)

modifica: mi sono reso conto che questo è un po 'inutile poiché ogni istanza indipendente ha la sua copia dei metodi pubblici, minando così l'uso del prototipo.


2

Ecco cosa mi è piaciuto di più finora riguardo ai metodi / membri privati ​​/ pubblici e all'istanza in javascript:

ecco l'articolo: http://www.sefol.com/?p=1090

ed ecco l'esempio:

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/


questo approccio ha una preoccupazione importante: se hai creato 2 oggetti, in memoria ci saranno 2 stessi methds (ad esempio PublicFunction) 1000 oggetti consumeranno tutta la tua memoria.
Artem G,

2

Il modello del modulo è corretto nella maggior parte dei casi. Ma se hai migliaia di istanze, le classi risparmiano memoria. Se il risparmio di memoria è un problema e i tuoi oggetti contengono una piccola quantità di dati privati, ma hanno molte funzioni pubbliche, allora vorrai che tutte le funzioni pubbliche vivano nel prototipo per risparmiare memoria.

Questo è quello che mi è venuto in mente:

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'oggetto privcontiene proprietà private. È accessibile tramite la funzione pubblica getPriv(), ma questa funzione ritorna a falsemeno che non la passi secret, e questo è noto solo all'interno della chiusura principale.


Questo simula i membri protetti, anche i tipi che ereditano da esso possono accedere ai membri protetti. Preferisco questo schema anche a quello privato
HMR,

2

Che dire di questo?

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 ricerca di variabili private è impossibile al di fuori dell'ambito della funzione immediata. Non c'è duplicazione di funzioni, risparmiando memoria.

Il rovescio della medaglia è che la ricerca di variabili private è goffa privateVars[this.id].cookedè ridicola da digitare. C'è anche una variabile "id" aggiuntiva.


Questo lascerà Restaurantcome undefinedperché non stai tornando nulla dalla funzione anonima.
user4815162342

Dove e come? Supponendo che il riferimento al ristorante creato venga perso, privateVars non avrà un riferimento al suo proprietario. Il grafico di riferimento è aciclico. Cosa mi sto perdendo?
Evan Leis,

In realtà questa è l'unica risposta che supporta proprietà private oltre ai metodi. Le uniche due questioni sono già state annotate nella risposta.
Pishpish,

Vedo una perdita di memoria: quando un'istanza di Restaurantè stata raccolta in modo inutile, i suoi valori rimangono all'interno privateVars. A WeakMappuò essere un buon sostituto per Arrayin questo caso.
Gershom,

2

Avvolgi tutto il codice in Funzione anonima: Quindi, tutte le funzioni saranno private, SOLO funzioni associate windowall'oggetto:

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

Usa questo :

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

VIOLINO


1

Preferisco archiviare dati privati ​​in un associato WeakMap. Ciò consente di mantenere i metodi pubblici sul prototipo a cui appartengono. Questo sembra essere il modo più efficiente per gestire questo problema per un gran numero di oggetti.

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

0

Le funzioni private non possono accedere alle variabili pubbliche utilizzando il modello di modulo


0

Dato che tutti pubblicavano qui il proprio codice, lo farò anch'io ...

Mi piace Crockford perché ha introdotto modelli reali orientati agli oggetti in Javascript. Ma ha anche escogitato un nuovo fraintendimento, quello "quello".

Quindi perché usa "that = this"? Non ha nulla a che fare con le funzioni private. Ha a che fare con le funzioni interne!

Perché secondo Crockford questo è un codice errato:

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

Quindi ha suggerito di fare questo:

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

Quindi, come ho detto, sono abbastanza sicuro che Crockford avesse sbagliato la sua spiegazione su questo e questo (ma il suo codice è certamente corretto). O stava solo ingannando il mondo Javascript, per sapere chi stava copiando il suo codice? Non so ... Non sono un fanatico del browser; D

MODIFICARE

Ah, questo è tutto: cosa significa 'var that = this;' intendi in JavaScript?

Quindi Crockie aveva davvero torto con la sua spiegazione ... ma proprio con il suo codice, quindi è ancora un ragazzo eccezionale. :))


0

In generale ho aggiunto temporaneamente l'oggetto privato _ all'oggetto. Devi aprire la privacy in modo esplicito nel "Costruttore di potenza" per il metodo. Se chiami il metodo dal prototipo, sarai in grado di sovrascrivere il metodo prototipo

  • Rendi accessibile un metodo pubblico nel "Costruttore di potenza": (ctx è il contesto dell'oggetto)

    ctx.test = GD.Fabric.open('test', GD.Test.prototype, ctx, _); // is a private object
  • Ora ho questo openPrivacy:

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

0

Questo è quello che ho elaborato:

Ha bisogno di una classe di codice zucchero che puoi trovare qui . Supporta anche roba protetta, ereditaria, virtuale, statica ...

;( 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'

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


0

Ho creato un nuovo strumento per consentirti di avere veri metodi privati ​​sul prototipo https://github.com/TremayneChrist/ProtectJS

Esempio:

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

1
Puoi spiegare come funziona, per favore? Come / dove puoi chiamare il _privatemetodo?
Bergi,

0

Devi mettere una chiusura attorno alla tua vera funzione di costruzione, dove puoi definire i tuoi metodi privati. Per modificare i dati delle istanze attraverso questi metodi privati, devi dare loro "questo" con loro, sia come argomento di funzione o chiamando questa funzione con .apply (questo):

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__);

In realtà, non funziona. Ognuno new Restaurantavrà il suo restaurantcostruttore e il "prototipo" viene totalmente abusato.
Bergi,

@Bergi. In realtà, hai ragione. Funzionerebbe ma sarebbe anche un maiale da risorsa (si chiama così?). Ho modificato la mia risposta al riguardo.
Flex Elektro Deimling

Grazie per l'aggiornamento. Non ho idea di come chiamare la versione precedente (ma "bug" :-)
Bergi

0

So che è un po 'troppo tardi, ma che ne dici?

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"));

0

Ci sono già molte risposte a questa domanda, ma nulla soddisfa i miei bisogni. Quindi ho trovato la mia soluzione, spero sia utile per qualcuno:

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

Come puoi vedere, questo sistema funziona quando si utilizza questo tipo di classi in JavaScript. Per quanto ho capito nessuno dei metodi commentati sopra ha fatto.


1
Curioso: il tuo bisogno era davvero di esporre la funzione ma renderla non operativa in fase di esecuzione - piuttosto che nasconderla dai chiamanti esterni come fanno tutte / la maggior parte delle altre risposte? Se è così, perché? Quali ritieni siano i vantaggi di questo approccio? Per me, questo sembra essere solo un sovraccarico di prestazioni non necessario, un'API poco chiara e, probabilmente, probabilmente destinata a causare un inferno di debug, ma sono sempre aperto a nuove prospettive ...
JHH,

2
@JHH a dire il vero, sono praticamente un palmo della faccia quando guardo indietro a questo. L'overhead generalmente non ne vale affatto la pena, anche se per me non ha avuto molta importanza in quanto non ho fatto molte chiamate a queste lezioni. La ragione per cui l'ho fatto in questo modo è solo che è relativamente pulito nel modo in cui scrivi e chiami le funzioni. Non capivo simboli e simili per il momento, ma ora che lo faccio, penso che generalmente sia la strada da percorrere quando si usano le lezioni. Sto pensando di rimuovere questa risposta tutti insieme. Ho pubblicato diverse risposte stupide, ma ehi, vivi e impari.
thegunmaster,

Grazie per il feedback! Non ero sicuro di aver frainteso qualcosa. Ma sì, viviamo e impariamo tutti!
JHH,

0

Vedi questa risposta per una soluzione 'class' pulita e semplice con un'interfaccia pubblica e privata e supporto per la composizione

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.