2009-07-25 15 views

risposta

14

Quello che stai facendo nel tuo esempio non è il modello di "classe" che le persone pensano di JS - in genere le persone pensano al modello di classe più "normale" di Java/C#/C++/etc che può essere simulato con le librerie.

Invece il vostro esempio è in realtà abbastanza normale e buon design JS, ma per completezza parlerò differenze di comportamento si vedrete tra i "membri" private e pubbliche avete

var private = "a private variable"; 
this.public = "a public variable"; 

Accesso private dall'interno una qualsiasi delle tue funzioni sarà molto più veloce dell'accesso a public perché la posizione di private può essere determinata ragionevolmente bene solo con una ricerca statica da parte del motore JS. I tentativi di accedere a public richiedono una ricerca, i motori JS più moderni eseguono un certo grado di cache di ricerca, ma è ancora più costoso di un semplice accesso varizzato.

var privatefn = function() { ... }; 
this.publicfn = function() { ... }; 

Le stesse regole di ricerca si applicano a queste funzioni con la variabile sopra accede, l'unica vera differenza (nel tuo esempio) è che se le funzioni sono chiamati, dicono privatefn() vs this.publicfn(), privatefn sarà sempre ottenere il mondiale oggetto per this. Ma anche se qualcuno fa

f = foo.publicfn; 
f(); 

Poi la chiamata a f avranno l'oggetto globale come thisma sarà in grado di modificare la variabile private.

Il modo più normale di eseguire le funzioni pubbliche (che risolve la funzione pubblica indipendente che modifica il problema dei membri privati) è di inserire funzioni pubbliche sul prototipo, ad es.

Foo.prototype.publicfn = function() { ... } 

che costringe funzioni pubbliche di non modificare le informazioni private - ci sono alcuni momenti in cui questo non è un'opzione, ma è buona pratica in quanto riduce anche l'uso della memoria un po ', prendere:

function Foo1() { 
    this.f = function(){ return "foo" }; 
} 

vs

function Foo2() { 
} 
Foo2.prototype.f = function(){ return "foo" }; 

In Foo1 si dispone di una copia dell'oggetto funzioni per ogni istanza di Foo1 (non tutto l'Emory, solo l'oggetto, per esempio. new Foo1().f !== new Foo2().f) mentre in Foo2 c'è solo un oggetto funzione singolo.

3

Questo è buono finora, ma c'è un altro livello di accesso che hai tralasciato.

this.publicfn è davvero un metodo privilegiato in quanto ha accesso a membri e funzioni privati.

Per aggiungere metodi che sono pubblici, ma non privilegiato, modificare il prototipo come segue:

FooClass.prototype.reallypublicfn = function() { ... }; 

nota che questo metodo non ha accesso ai membri privati ​​della FooClass ma è accessibile attraverso qualsiasi istanza di FooClass.

Un altro modo di realizzare questo sta tornando questi metodi dal costruttore

var FooClass = function() 
{ 
    var private = "a private variable"; 
    this.public = "a public variable"; 

    var privatefn = function() { ... }; 
    this.publicfn = function() { ... }; 

    return { 
    reallypublicfn: function() { ...} 
    } 
}; 

var foo = new FooClass(); 
foo.public = "bar"; 
foo.publicfn(); 

In sostanza, questi metodi di dati nasconde aiuto, se effettuata secondo le tecniche tradizionali OOP. In generale, migliorare la protezione dei dati e l'incapsulamento nelle tue classi è una buona cosa. Garantire un accoppiamento basso rende molto più semplice cambiare le cose lungo la strada, quindi esporre pubblicamente il meno possibile è davvero a tuo vantaggio.

Vedere https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript per una panoramica semplice e http://www.crockford.com/javascript/private.html per i dettagli su come realizzare queste cose.

+1

Il vostro esempio FooClass non è corretto. La semantica del nuovo è effettivamente: "var foo = {}; foo .__ proto__ = FooClass.prototype; var temp = FooClass.call (temp); if (IsObject (temp)) foo = temp;". Questo significa che nel tuo esempio foo sarà il nuovo oggetto "{reallypublicfn: function() {...}}" quindi non avrà il prototipo FooClass o la funzione publicfn – olliej

3

Lo svantaggio principale è che ti ritroverai con una copia di publicfn per ogni istanza di FooClass. Se sarete creare un sacco di oggetti FooClass, sarebbe più efficiente per scrittura

FooClass.prototype.publicfn = function() { ... }; 
+1

nota che questi due modi di creare publicfn non sono equivalenti . uno è privilegiato e l'altro no –

1

Dipende dalle vostre esigenze e performance relativa. Javascript non è la lingua più sicura per i caratteri e non è molto forte per quanto riguarda la visibilità dei membri. Tradizionalmente puoi avere visibilità "privata", "pubblica privilegiata" e "pubblica" all'interno di un tipo Javascript.

È possibile dichiarare i membri privati ​​e pubblici privilegiati con:

function FooClass() 
{ 
    var privateVar = 1; 
    function privateFn() 
    { 
     return privateVar; // etc... 
    } 
    this.publicVar = 2; 
    this.publicFn = function() 
    { 
     return privateFn(); 
    } 
} 

questo esempio viene utilizzata una chiusura di funzione, che consiste in una dichiarazione di funzione che include i valori dal campo di applicazione in cui si definisce la funzione. Questo è accettabile quando la visibilità dei membri è necessaria ma può portare a un sovraccarico. L'interprete JavaScript non può riutilizzare le definizioni privateFn o publicFn per ogni istanza poiché si riferiscono a variabili o funzioni nell'ambito esterno. Di conseguenza ogni istanza di FooClass risulta in uno spazio di archiviazione aggiuntivo per privateFn e publicFn. Se il tipo viene utilizzato raramente o con moderazione, la penalità delle prestazioni è neglegabile. Se il tipo viene usato molto spesso nella pagina, o se la pagina è più di uno stile "AJAX" in cui la memoria non viene liberata frequentemente poiché la pagina non viene scaricata, la penalità può essere più visibile.

Un approccio alternativo consiste nell'utilizzare i membri del prototipo. Questi sono membri pubblici non protetti. Dal momento che Javascript non è completamente sicuro per il tipo ed è relativamente facile da modificare dopo il caricamento, digitare la sicurezza e la visibilità dei membri non sono così affidabili per controllare l'accesso ai tipi interni. Per motivi di prestazioni, alcuni framework come Ajax di ASP.NET utilizzano invece la denominazione dei membri per inferire le regole di visibilità. Ad esempio, lo stesso tipo in ASP.NET Ajax potrebbe sembrare:

function FooClass2() 
{ 
    this._privateVar = 1; 
    this.publicVar = 2; 
} 
FooClass2.prototype = 
{ 
    _privateFn : function() 
    { 
     return this._privateVar; 
    }, 
    publicFn : function() 
    { 
     return this._privateFn(); 
    } 
} 
FooClass2.registerClass("FooClass2"); 

In questo caso i membri privati ​​di ambito sono privati ​​solo di nome, il prefisso "_" è considerata la seguente una variabile membro privata. Ha il rovescio della medaglia di impedire al membro di essere veramente privato, ma il vantaggio di consentire l'applicazione di patch in memoria dell'oggetto. L'altro vantaggio principale è che tutte le funzioni vengono create una volta dall'interprete e dal motore e riutilizzate più e più volte per il tipo. La parola chiave "this" quindi fa riferimento all'istanza del tipo anche se il riferimento alla funzione stessa è lo stesso.

Un modo di vedere la differenza in azione è quello di provare questo con entrambi i tipi (se non si dispone di ASP.NET Ajax, è possibile ignorare l'ultima riga FooClass2 che chiama registerClass())

var fooA = new FooClass(), fooB = new FooClass(); 
alert(fooA.publicFn===fooB.publicFn); // false 

var foo2A = new FooClass2(), foo2B = new FooClass2(); 
alert(foo2A.publicFn===foo2B.publicFn); // true 

Quindi è una questione di sicurezza del tipo e visibilità dei membri vs.prestazioni e la possibilità di patch in memoria

0

Inoltre, se posso menzionare qualcosa di utile a questo - Con prototype è possibile aggiungere ulteriori metodi in seguito nel codice per estendere la funzione da un ambito esterno. Come un piccolo vantaggio, l'intero corpo con tutti i metodi non sarà reso ogni volta, quindi accelera le prestazioni di compilazione. Se hai già tutti i metodi dichiarati all'interno della funzione, questo richiederà più tempo per il rendering. Quindi, il mio consiglio, applicare questi ultimi solo quando diventano rilevanti nel codice corrente.

Esempio:

// inside 
function fn(arg) { 
    this.val = arg; 
    fn.prototype.getVal =()=> { 
     console.log(this.val); 
    } 
} 
var func = new fn('value'); 
func.getVal(); 


// declare extern methods 
function fn2(arg) { 
    this.val = arg; 
} 
fn2.prototype.getVal =()=> { 
    console.log(this.val); 
} 
var func2 = new fn2('value'); 
func2.getVal();