"Metodi" privati e il referente this, una "simbiosi univoca"
Sanno entrambi di esistere, convivono all'interno dello stesso spazio, ma solo uno è in grado di sfruttare l'altro.
Utilizzare un this all'interno di una funzione privata è infatti quanto di più sbagliato si possa fare durante la scrittura di una classe. Essenso la funzione privata va gestita esattamente come tale, ovvero come una funzione che, non avendo un referente this, non è parte di un oggetto (perlomeno non direttamente associata ad esso) e se richiamata non avrà alcun this da sfruttare, se non quello della super classe window.
Questo è un "hack" particolare, perchè permette ad una classe di risalire direttamente a variabili globali attraverso l'uso di funzioni interne ma può forviare i meno preparati, poichè il this in questione sarà tutto tranne l'oggetto stesso dove tale funzione è stata definita.
codice:
function Test(){
function avviso(){alert(this.oggetto)};
this.avviso = function() {
avviso();
};
};
var oggetto = new Test();
oggetto.avviso(); // [object Object]
In questo esempio il metodo avviso richiama la funzione avviso che mostra in alert l'oggetto stesso grazie al fatto che var oggetto è parte della super classe window.
Niente di più "caotico" ai fini dell'utilizzo di classi o oggetti, sia perchè non è detto che il nome dell'oggetto sia sempre noto come non è detto che sia sempre globale.
Le classi possono infatti essere anche innestate (classi definite dentro altre classi ... classi "private") ed in questo caso è pressapoco impossibile risalire all'oggetto che si sta manipolando.
Per ovviare a questa serie di problematiche, è sufficiente ricordarsi di inviare il referente stesso alle funzioni interne che lo richiedono.
codice:
function Test(){
function avviso(self){
alert(self.parametro);
};
this.avviso = function() {
avviso(this);
};
this.parametro = "Ciao Mondo";
};
var oggetto = new Test();
oggetto.avviso(); // Ciao Mondo
Ho letto tutto, sembra OK ... ma che fine ha fatto prototype ?
Per molti sviluppatori JS prototype è "l'unico modo di scrivere oggetti in JavaScript" o spesso considerato "l'unico modo corretto di scrivere oggetti in JavaScript" ... niente di più discutibile dal mio punto di vista.
L'attributo prototype non è ne l'unico modo di creare oggetti ne il modo corretto, poichè
- estranea il più delle volte porzioni di codice dalla classe di appartenenza
- rende il codice poco leggibile
- viene usato anche quando non ce n'è assoluto bisogno
codice:
// classe vista fino ad ora
function Test1() {
this.parametro = "Test1";
this.mostraParametro = function() {
alert(this.parametro);
};
};
// classe secondo molti ...
function Test2(){};
Test2.prototype.parametro = "Test2";
Test2.prototype.mostraParametro = function(){
alert(this.parametro);
};
Dal punto di vista della leggibilità sfido chiunque a dire che il metodo prototype sia migliore mentre dal punto di vista puramente pratico con prototype non è possibile utilizzare una funzione interna con scope globale all'interno della classe, cosa secondo me in certi casi estremamente utile, se lo scopo è simulare metodi privati altrimenti inesistenti.
codice:
function Test2(){
function avviso(self){alert(self.parametro)};
};
Test2.prototype.parametro = "Test2";
Test2.prototype.mostraParametro = function(){
avviso(this);
};
var test2 = new Test2();
test2.mostraParametro(); // avviso is not defined
Non saranno mica tutti deficenti che usano prototype ...
Infatti non lo sono! Uno dei vantaggi principale del metodo prototype è l'assegnazione "in core" del metodo / parametro. Questo significa che mentre un this.funzione deve essere in qualche modo rivalutato ad ogni chiamata dal motore JavaScript del browser, un metodo prototype non costringe tale motore a fare questo passaggio.
In soldoni, il metodo prototype è uno dei metodi consigliati qualora la velocità di esecuzione del codice sia uno degli obiettivi principali ma bisogna anche saper valutare quando questa ottimizzazione sia veramente in grado di permettere al codice di trarne vantaggi.
Una constatazione personale (mia e di altri) sulla questione è che tanto vale, a questo punto, sfruttare questa caratteristica direttamente per tutta la classe, al fine di non dover rivalutare ne l'assegnazione della stessa ne alcuna parte del suo codice.
codice:
function Test() {
function avviso(self){alert(self.parametro)};
this.parametro = "Test";
this.mostraParametro = function() {
avviso(this);
};
};
// prototype di se stessa
Test.prototype = new Test;
var test = new Test();
test.mostraParametro();
Un'alternativa potrebbe essere, qualora l'inizializzazione della classe dovesse effettuare a sua volta operazioni, dichiarare le prototype direttamente in scrittura classe.
codice:
function Test() {
function avviso(self){alert(self.parametro)};
this.parametro = "Test";
Test.prototype.mostraParametro = function() {
avviso(this);
};
};
var test = new Test();
test.mostraParametro();
Nota di rilievo è l'utilizzo di Test, al posto di this, in assegnazione metodo ed a sua volta l'utilizzo voluto di this, invece che Test.prototype, per l'assegnazione del parametro.
Questo punto è fondamentale per capire l'uso di prototype, spiegato ora.
Ma cosa fa di preciso prototype ?
prototype specifica al motore JavaScript del browser che "quella" determinata classe deve avere quel metodo o quel parametro. Per "quella" non si intende quella sola, ma tutte le variabili che ne derivano.
codice:
function Test() {
Test.prototype.saluta = function() {
alert(this.saluto);
};
};
// istanzio 2 oggetti classe Test
var test = new Test();
var altroTest = new Test();
// imposto il parametro saluto per ogni oggetto
test.saluto = "Ciao";
altroTest.saluto = "Mondo";
// sfrutto il metodo
test.saluta(); // Ciao
altroTest.saluta(); // Mondo
// ridefinisco la prototype della classe Test
Test.prototype.saluta = function() {
alert("Prototype");
};
// tento di risfruttare il metodo
test.saluta(); // Prototype
Va anche detto che avendo scelto di sfruttare il metodo prototype in assegnazione classe, il metodo saluta verrebbe comunque ridefinito in modo corretto.
codice:
// .... codice sopra ... ed in fine
Test();
test.saluta(); // Ciao
Abbastanza intrecciata come situazione ... e sapete quale è l'unico modo per non vedersi derubati del proprio metodo per ogni oggetto istanziato o per la classe stessa ? Non usare prototype !!!
codice:
function Test() {
this.saluta = function() {
alert(this.saluto);
};
};
// istanzio 2 oggetti classe Test
var test = new Test();
var altroTest = new Test();
// imposto il parametro saluto per ogni oggetto
test.saluto = "Ciao";
altroTest.saluto = "Mondo";
// sfrutto il metodo
test.saluta(); // Ciao
altroTest.saluta(); // Mondo
// ridefinisco la prototype della classe Test
Test.prototype.saluta = function() {
alert("Prototype");
};
// tento di risfruttare il metodo
test.saluta(); // Ciao
altroTest.saluta(); // Mondo
Ebbene il super metodo prototype non prevale sul referente this poichè sebbene di fatto venga assegnato alla classe Test un metodo diverso da quello definito al suo interno senza prototype, le variabili derivate, come detto all'inizio di questa pillola, non sono la funzione Test, ma sono oggetti istanziati da una classe e non sfruttano più quindi un metodo di funzione esteso attraverso l'uso di prototype ma sfruttano semplicemente un metodo assegnato a loro stessi, un pò come aver fatto questo:
codice:
var oggetto = {
saluta:function() {alert("Ciao");}
};
oggetto.saluta(); // Ciao
oggetto.prototype.saluta = function(){
alert("Prototype");
}; // Errore: oggetto.prototype has no properties
Tutto ciò è per far capire che nel caso del this.metodo = function(){} ... per riassegnare o sovrascrivere tale metodo a tutti gli oggetti deriivati da quella classe è necessario ricercarli tutti e riassegnare il metodo "a mano" per ognuno di questi e questo può essere sia un pro a favore dell'integrità che un contro a causa della difficoltà maggiore qualora sia veramente necessario ad un certo punto del codice cambiare uno o più metodi ad una serie di oggetti derivati da una o più classi (ma a questo punto è più facile che il codice abbia qualcosa che non va a livello di concetto base ...).
Ma prototype serve solo ad assegnare metodi ?
No, serve anche ad altro.
prototype serve ad estendere classi native
codice:
var test = "Mondo";
// per estendere tutte le stringhe
String.prototype.saluta = function() {
alert("Ciao");
};
test.saluta(); // Ciao
// per estendere tutti gli oggetti e derivati, quindi stringhe comprese
Object.prototype.hello = function() {
alert("Hello");
};
test.hello(); // Hello
[].hello(); // Hello
(1).hello(); // Hello
(true).hello(); // Hello
e serve quindi anche ad estendere classi JavaScript.
codice:
function Test() {
this.saluta = function() {
alert("Ciao");
};
this.salutaTest = function() {
alert("Ciao Test");
};
};
function SubTest() {
this.saluta = function() {
alert("Ciao SubTest");
};
};
SubTest.prototype = new Test;
var subtest = new SubTest();
subtest.saluta(); // Ciao SubTest
subtest.salutaTest(); // Ciao Test
Per concludere
Una cosa è certa, JavaScript non ha linearità concettuale paragonabile a Java, C# o altri linguaggi, ma tutto sommato ne ha comunque una ben definita. Tutto sta fare tests, capire al meglio cosa prevale su cosa, scope globale, locale, referenti e probabilmente anche altro, qui ho solo tentato di dare una piccola delucidazione sulla questione, spero di aver fatto cosa gradita.
Vi lascio meditare su quest'ultimo pezzo di codice 
codice:
function Test() {
this.saluta = function() {
alert("Ciao");
};
this.salutaTest = function() {
alert("Ciao Test");
};
this.getNested = function(saluto) {
function getNested(message){alert(message)};
this.salutoInCostruzione = saluto;
this.saluta = function() {
getNested(this.salutoInCostruzione);
};
};
this.salutaDallaSub = function() {
this.saluta(); // Ciao SubTest
};
};
function SubTest() {
this.saluta = function() {
alert("Ciao SubTest");
};
this.salutaSuperNested = function() {
var super = new this.constructor();
super.saluta(); // Ciao
new super.getNested("Nested").saluta(); // Nested
};
};
SubTest.prototype = new Test;
var subtest = new SubTest();
subtest.saluta(); // Ciao SubTest
subtest.salutaTest(); // Ciao Test
subtest.salutaSuperNested(); // Ciao, Nested
subtest.salutaDallaSub(); // Ciao SubTest
capito questo, siete a cavallo