2009-01-27 13 views
17

Beh, ho cercato di capire che questo è possibile in qualsiasi modo. Ecco il codice:Javascript membro privato su prototipo

a=function(text) 
{ 
    var b=text; 
    if (!arguments.callee.prototype.get) 
     arguments.callee.prototype.get=function() 
    { 
     return b; 
    } 
    else 
     alert('already created!'); 
} 

var c=new a("test"); // creates prototype instance of getter 
var d=new a("ojoj"); // alerts already created 
alert(c.get())  // alerts test 
alert(d.get())  // alerts test from context of creating prototype function :(

Come vedi ho cercato di creare prototipi getter. Per cosa? Beh, se si scrive qualcosa di simile:

a=function(text) 
{ 
    var b=text; 
    this.getText=function(){ return b} 
} 

... tutto dovrebbe andare bene .. ma in realtà ogni volta che creare l'oggetto - creo funzione getText che utilizza la memoria. Mi piacerebbe avere una funzione prototipo che giace nella memoria che farebbe lo stesso ... Qualche idea?

EDIT:

ho provato soluzione proposta dal Christoph, e sembra che la sua soluzione nota solo per ora. È necessario ricordare le informazioni sull'ID per recuperare il valore dal contesto, ma l'intera idea è piacevole per me :) L'id è solo una cosa da ricordare, tutto il resto può essere memorizzato una volta nella memoria. In effetti è possibile memorizzare un sacco di membri privati ​​in questo modo e utilizzare sempre un solo ID. In realtà questo mi sta soddisfacendo :) (a meno che qualcuno non abbia un'idea migliore).

someFunc = function() 
{ 
    var store = new Array(); 
    var guid=0; 
    var someFunc = function(text) 
    { 
    this.__guid=guid; 
    store[guid++]=text; 
    } 

    someFunc.prototype.getValue=function() 
    { 
    return store[this.__guid]; 
    } 

    return someFunc; 
}() 

a=new someFunc("test"); 
b=new someFunc("test2"); 

alert(a.getValue()); 
alert(b.getValue()); 
+0

questo sembra un duplicato di [un'altra domanda] (http://stackoverflow.com/questions/436120) –

risposta

28

JavaScript tradizionalmente non ha fornito un meccanismo per nascondere la proprietà ("membri privati").

Dato che JavaScript ha un ambito lessicale, è sempre possibile simularlo a livello di oggetto, utilizzando la funzione di costruzione come chiusura sui "membri privati" e definendo i metodi nel costruttore, ma ciò non funzionerà per metodi definiti nella proprietà prototype del costruttore.

Naturalmente, ci sono modi per aggirare questo, ma io non lo consiglio:

Foo = (function() { 
    var store = {}, guid = 0; 

    function Foo() { 
     this.__guid = ++guid; 
     store[guid] = { bar : 'baz' }; 
    } 

    Foo.prototype.getBar = function() { 
     var privates = store[this.__guid]; 
     return privates.bar; 
    }; 

    Foo.prototype.destroy = function() { 
     delete store[this.__guid]; 
    }; 

    return Foo; 
})(); 

Ciò memorizzare le proprieta 'private' in un altro separato oggetto dalla Foo esempio. Assicurati di chiamare destroy() dopo aver finito con l'oggetto: altrimenti hai appena creato una perdita di memoria.


modifica 2015/12/01: ECMAScript6 fa varianti migliorate che non richiedono distruzione oggetto manuale possibile, ad esempio tramite un WeakMap o preferibilmente un Symbol, evitando la necessità di un deposito esterno del tutto:

var Foo = (function() { 
    var bar = Symbol('bar'); 

    function Foo() { 
     this[bar] = 'baz'; 
    } 

    Foo.prototype.getBar = function() { 
     return this[bar]; 
    }; 

    return Foo; 
})(); 
+0

Non significa che creo un oggetto funzione completamente nuovo ogni volta che chiamo il nuovo Foo() ?? – Wilq32

+0

Qualcosa non sembra proprio qui ... come si gestisce questo quando ci sono 2 istanze della classe Foo? –

+0

oh, capisco ... il "negozio" è condiviso da tutta la classe, ma la voce nel negozio è per istanza. –

2

I metodi sul prototipo non possono accedere ai membri "privati" così come sono presenti in javascript; hai bisogno di un qualche tipo di accesso privilegiato. Dato che stai dichiarando get dove può vedere lessicamente b, restituirà sempre ciò che b era al momento della creazione.

2

Dopo essere stato enormemente ispirato dal work-around di Christoph, mi è venuto in mente un concetto leggermente modificato che fornisce alcuni miglioramenti. Ancora una volta, questa soluzione è interessante, ma non necessariamente raccomandata.Questi miglioramenti includono:

  • Non è più necessario eseguire alcuna configurazione nel costruttore
  • rimosso la necessità di memorizzare un GUID pubblica sulle istanze
  • Aggiunto po 'di zucchero sintattico

In sostanza il trucco qui è utilizzare l'oggetto istanza stesso come chiave per accedere all'oggetto privato associato. Normalmente questo non è possibile con oggetti semplici dato che le loro chiavi devono essere stringhe. Tuttavia, sono stato in grado di farlo utilizzando il fatto che l'espressione ({} === {}) restituisce false. In altre parole, l'operatore di confronto può distinguere tra tra istanze di oggetto univoche.

Per farla breve, siamo in grado di utilizzare due array per mantenere le istanze ed i loro oggetti privati ​​associati:

Foo = (function() { 
    var instances = [], privates = []; 

    // private object accessor function 
    function _(instance) { 
     var index = instances.indexOf(instance), privateObj; 

     if(index == -1) { 
      // Lazily associate instance with a new private object 
      instances.push(instance); 
      privates.push(privateObj = {}); 
     } 
     else { 
      // A privateObject has already been created, so grab that 
      privateObj = privates[index]; 
     } 
     return privateObj; 
    } 

    function Foo() { 
     _(this).bar = "This is a private bar!"; 
    } 

    Foo.prototype.getBar = function() { 
     return _(this).bar; 
    }; 

    return Foo; 
})(); 

Noterete la funzione _ sopra. Questa è la funzione di accesso per afferrare l'oggetto privato. Funziona pigramente, quindi se lo chiami con una nuova istanza, creerà al volo un nuovo oggetto privato.

Se non si desidera duplicare il codice _ per ogni classe, si può risolvere questo avvolgendolo all'interno di una funzione di fabbrica:

function createPrivateStore() { 
    var instances = [], privates = []; 

    return function (instance) { 
     // Same implementation as example above ... 
    }; 
} 

Ora è possibile ridurre ad una sola riga per ogni classe :

var _ = createPrivateStore(); 

Anche in questo caso, è necessario essere molto attenti utilizzo di questa soluzione in quanto può creare perdite di memoria se non implementare e chiamare un distruggere functi su quando necessario.

-1

Ho creato una nuova libreria per abilitare i metodi privati ​​sulla catena del 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 
0

Personalmente, non mi piace molto la soluzione con il GUID, perché costringe lo sviluppatore di dichiarare in aggiunta al negozio e ad incrementarlo nel costruttore. Nelle applicazioni javascript di grandi dimensioni gli sviluppatori potrebbero dimenticare di farlo, il che è abbastanza incline agli errori.

Mi piace la risposta di Peter più o meno per il fatto che è possibile accedere ai membri privati ​​utilizzando il contesto (questo). Ma una cosa che mi dà fastidio è il fatto che l'accesso ai membri privati ​​avviene in una (o) complessità. In effetti, trovare l'indice di un oggetto in array è un algoritmo lineare. Considera di voler utilizzare questo modello per un oggetto che è instanciato 10000 volte. Quindi è possibile eseguire iterazioni su 10000 istanze ogni volta che si desidera accedere a un membro privato.

Per accedere a negozi privati ​​in una o (1) complessità, non c'è altro modo che usare i guids.Ma al fine di non perdere tempo con la dichiarazione guid e Incremento e al fine di utilizzare il contesto per accedere al negozio privato ho modificato Peters modello di fabbrica come segue:

createPrivateStore = function() { 
var privates = {}, guid = 0; 

return function (instance) { 
    if (instance.__ajxguid__ === undefined) { 
     // Lazily associate instance with a new private object 
     var private_obj = {}; 
     instance.__ajxguid__ = ++guid; 
     privates[instance.__ajxguid__] = private_obj; 
     return private_obj; 
    } 

    return privates[instance.__ajxguid__]; 
} 

}

Il trucco è quello di prendere in considerazione che gli oggetti che non hanno la proprietà ajxguid non sono ancora gestiti. In effetti, è possibile impostare manualmente la proprietà prima di accedere al negozio per la prima volta, ma penso che non ci sia una soluzione magica.

2

Con i browser moderni che adottano alcune tecnologie ES6, è possibile utilizzare WeakMap per aggirare il problema GUID. Questo funziona in IE11 e sopra:

// Scope private vars inside an IIFE 
var Foo = (function() { 
    // Store all the Foos, and garbage-collect them automatically 
    var fooMap = new WeakMap(); 

    var Foo = function(txt) { 
     var privateMethod = function() { 
      console.log(txt); 
     }; 
     // Store this Foo in the WeakMap 
     fooMap.set(this, {privateMethod: privateMethod}); 
    } 

    Foo.prototype = Object.create(Object.prototype); 
    Foo.prototype.public = function() { 
     fooMap.get(this).p(); 
    } 
    return Foo; 
}()); 

var foo1 = new Foo("This is foo1's private method"); 
var foo2 = new Foo("This is foo2's private method"); 
foo1.public(); // "This is foo1's private method" 
foo2.public(); // "This is foo2's private method" 

WeakMap non memorizzerà i riferimenti a qualsiasi Foo dopo che viene eliminato o si rientra nell'ambito, e dal momento che usa gli oggetti come chiavi, non è necessario allegare al GUID il tuo oggetto.

+0

+1, ma se si utilizza ES6, perché non utilizzare [simboli] (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Riferimento/Global_Objects/Symbol) – Christoph

+0

@Christoph: perché i simboli sono collegati all'oggetto e puoi ottenerli usando 'Object.getOwnPropertySymbols()'. Probabilmente lo hai già scoperto ... – jimasun

Problemi correlati