Sei sulla pagina 1di 23

JavaScript Object Oriented

C' chi dice non sia utile, c' chi dice non sia argomento semplice tanto quanto java o C# ... di fatto sviluppare JS in questo modo ne comporta i soliti vantaggi della programmazione ad oggetti, soprattutto oggi che JS sta prendendo sempre pi piede, attraverso una semplicit nota ed intrinseca, tipica del JavaScript stesso. Questa pillola non pensata per chi a digiuno di JavaScript, bens per chi ne conosce potenzialit e sintassi ma non l'ha ancora approfondito con gli oggetti.

Cosa si intende per classe


Una classe una porzione di codice portabile e riutilizzabile in grado di effettuare una o pi operazioni attraverso metodi (funzioni interne pubbliche o private) o di restituire informazioni attraverso i parametri (variabili interne pubbliche o private).

Una classe in JavaScript una funzione ?


Si, possibile affermarlo, ma non altrettanto possibile farlo nei confronti dell'oggetto che ne istanzia una. Se una classe di fatto un instanceof Function, un'istanza della classe non un instanza di funzione ma esattamente un'istanza del nome di quella funzione, ovvero un'istanza di quella classe, o per comodit un oggetto "Classe".
function Test(){}; // funzione Test var test = new Test(); // oggetto Test // Test istanziato come classe [grazie a new] alert(Test instanceof Function); alert(test instanceof Function); alert(test instanceof Test); // true // false // true

Una funzione pu quindi essere usata sia come classe che come funzione ?
// funzione Test function Test(){ // assegnazione di un parametro interno pubblico this.parametro = "Ciao Mondo"; // operazione di funzione alert(this.parametro); }; // chiamata a funzione Test Test(); // Ciao Mondo // istanza di un nuovo oggetto Test var test = new Test(); // Ciao Mondo

La differenza fondamentale tra l'utilizzo della funzione Test come tale o come classe, che nel secondo caso possiamo sfruttare in un secondo momento il parametro interno.
// richiamo il parametro della funzione alert(Test.parametro); // undefined // richiamo il parametro dell'oggetto di tipo Test alert(test.parametro); // Ciao Mondo

Questo accade perch il riferimento all'interno della classe Test, ovvero il valore this, perde il referente di se stesso non appena la funzione cessa la sua esecuzione e nel caso di una chiamata diretta, Test.parametro, questo valore di fatto come se non esistesse, visto che la funzione Test non stata richiamata e visto che this.parametro solo una parte di codice presente all'interno di questa funzione.

alert(new Test().parametro); // Ciao Mondo alert(Test().parametro); // Errore: Test has no properties

Per questo necessario istanziare un oggetto attraverso l'uso di Test come classe e non come funzione (sempre grazie a new), per permettere al codice interno alla classe di risalire a se stesso in qualunque momento, perlomeno finch l'oggetto istanziato ha una validit all'interno del proprio scope.

Scope ? ... e perch non Rubamezzetto ?


Lo scope lo spazio in cui una variabile, una funzione o in generale un oggetto, risulta essere raggiungibile o utilizzabile. Lo scope indispensabile per capire gli oggetti JavaScript ed il JavaScript stesso. Una volta capito alla perfezione come procedere per avere o non avere un determinato scope per ogni metodo, funzione, variabile o oggetto che sia, la strada sar tutta in discesa.
// funzione Test function Test(){ // parametro interno pubblico this.parametro = "Ciao Mondo"; // parametro interno privato var parametro = "Ciao Mondo Parallelo"; // operazione di funzione alert(parametro); }; // istanza di un nuovo oggetto Test var test = new Test(); // Ciao Mondo Parallelo alert(test.parametro); // Ciao Mondo

La variabile parametro all'interno di Test non cessa comunque di esistere dopo l'assegnazione ad oggetto, poich il suo scope persiste all'interno di quella porzione di codice.
// funzione Test function Test(){ // parametro privato var parametro = "Ciao Mondo Parallelo"; // parametro pubblico this.parametro = "Ciao Mondo"; // metodo pubblico this.leggiParametro = function() { alert(parametro); }; }; var test = new Test(); test.leggiParametro(); alert(test.parametro); test.leggiParametro(); // // // // ... non accade niente ... Ciao Mondo Parallelo Ciao Mondo Ciao Mondo Parallelo

Ma la parola var prima del nome della variabile indispensabile ?


La parola var una delle migliore amiche di JavaScript ed quella che definisce esattamente lo scope di una variabile.
// una variabile globale non ha bisogno della parola var // ma non nemmeno sbagliato usare la parola // var per dichiarare una globale var parametro = "Variabile Globale"; function Test(){ // parametro privato senza var parametro = "Ciao Mondo Parallelo"; this.leggiParametro = function() { alert(parametro); }; }; alert(parametro); // Variabile Globale var test = new Test(); test.leggiParametro(); // Ciao Mondo Parallelo alert(parametro); // Ciao Mondo Parallelo

Dichiarando invece la variabile di funzione con var non si rischia di sovrascrivere una globale ne di ritrovarsi sovrascritta la variabile di funzione.
function Test(){ // parametro privato senza var parametro = "Ciao Mondo Parallelo"; this.leggiParametro = function() { alert(parametro); }; }; var test = new Test(); test.leggiParametro(); // Ciao Mondo Parallelo

var parametro = "Variabile Globale"; test.leggiParametro(); // Variabile Globale

Con var possiamo quindi stare sempre tranquilli


function Test(){ var parametro = "Ciao Mondo Parallelo"; this.leggiParametro = function() {alert(parametro);}; }; var parametro = "Variabile Globale"; var test = new Test(); test.leggiParametro(); // Ciao Mondo Parallelo alert(parametro); // Variabile Globale test.leggiParametro(); // Ciao Mondo Parallelo

Ma se ho una variabile privata non posso risalire a quella globale ?


Una variabile globale come se fosse un parametro della super classe window, quindi sempre possibile raggiungere il suo valore anche usando omonimi all'interno di una classe.
function Test(){ var parametro = "Ciao Mondo Parallelo"; this.leggiParametro = function() {alert(parametro);}; this.leggiParametroGlobale = function(){ alert(window.parametro); }; }; var parametro = "Variabile Globale"; var test = new Test(); test.leggiParametroGlobale(); // Variabile Globale test.leggiParametro(); // Ciao Mondo Parallelo

Perch usare variabili private e non globali all'interno di una classe ?


Non c' un motivo assoluto per scegliere di usare una variabile privata piuttosto che una globale se non quello di essere certi che il valore della variabile o del valore resttuito dalla funzione privata sar esattamente quello che ci aspettiamo all'interno dell'oggetto e che nessuno potr quindi modificarlo. Uno dei limiti pi consistenti della programmazione ad oggetti in JavaScript infatti proprio quello di non poter garantire consistenza agli oggetti. Per consistenza si intende la possibilit di impedire ad altri di modificare metodi o parametri pubblici dei nostri oggetti.
function Test(){ this.metodoPubblico = function() { alert("Ciao Mondo"); }; }; var test = new Test(); test.metodoPubblico(); // Ciao Mondo // un altra porzione di codice fa, "per sbaglio", questa operazione test.metodoPubblico = "Ciao Mondo"; // operazione sempre lecita per un oggetto // il metodo pubblico dell'oggetto test stato distruto test.metodoPubblico(); // Error: test.metodoPubblico is not a function

Qualora un metodo od una variabile dovessero essere utili solo per poter utilizzare altri metodi pubblici consigliabile sfruttare funzioni o variabili interne, quindi private, al fine di limitare la possibilit ad altri di modificare porzioni indispensabili di codice.

Ma perch continui a dire "altri"? Sono io che scrivo le classi ed il codice ... !!!
Le considerazioni da fare sono almeno un paio in applicativi dove il codice veramente tanto e le classi create da centinaia di linee pu capitare di "dimenticarsi" di un nome di metodo o parametro gi usati per altri scopi, limitare l'utilizzo di nomi al fine di avere pubblici solo quelli fondamentali per l'oggetto pu essere di aiuto JavaScript si include nelle pagine con una facilit impressionante, Google, come la super mega libreria "Cinema Effects with only 200Kb", potrebbero in qualche modo cambiare il vostro codice e viceversa, senza usare i giusti accorgimenti potreste voi stessi distruggere il codice di altre librerie presenti che avete incluso nella pagina

Ok per le variabili, qualcosa sulle funzioni / metodi privati ?


Assegnare un parametro o un metodo pubblico molto semplice, basta usare this davanti al nome del metodo o parametro. Allo stesso tempo all'interno di un metodo pubblico il this sar riferito all'oggetto stesso.
function Test(){ this.confronta = function(variabile) { alert(this === variabile); }; }; var test = new Test(); test.confronta(test); // true test.confronta(Test); // false

Questo permette al metodo pubblico di risalire ad altri metodi pubblici o variabili pubbliche attraverso l'uso del prefisso this, riferendosi sempre all'oggetto stesso.
function Test(){ this.mostraPippo = function() { alert(this.pippo); } }; var test = new Test(); var oggetto = new Test(); oggetto.pippo = "Yuk!"; oggetto.mostraPippo(); // Yuk! test.mostraPippo(); // undefined

Quanto detto serve per introdurre i metodi privati, o per meglio definirli, le funzioni a scope locale. Queste funzioni possono essere innestate nella classe come nel metodo pubblico e sebbene non abbiano la parola var davanti hanno le stesse identiche regole di scope delle variabili.
function Test(){ // "metodo" privato / funzione privata function avviso(){alert("Avviso Generico")}; this.avviso = function() { avviso(); }; this.mostraAvviso = function(messaggio) { // funzione locale function avviso(){alert(messaggio)}; avviso(); }; }; var test = new Test(); test.mostraAvviso("Ciao Mondo"); test.avviso(); // Ciao Mondo // Avviso Generico

In questo "semplice" esempio c' molto di quanto detto sullo scope, sulle funzioni innestate e sui metodi privati. E' importante notare che la funzione locale al metodo mostraAvviso nonsovrascrive quella privata definita nello scope della classe (la prima), come importante notare che la funzione innestata al metodo mostraAvviso non ha in questo caso nemmeno bisogno di una variabile come argomento, poich lo scope della variabile messaggio, inviata al metodo dell'oggetto, ha validit per tutto il metodo stesso ed ogni porzione di codice creata al suo interno potr sfruttare questa variabile. Altra nota di rilievo che in questo caso non possibile, una volta oscurato lo scope della prima funzione "avviso()", risalire alla stessa, poich this.avviso, all'interno del metodo, come Test.

Avviso, sempre all'interno o fuori dall'oggetto, non saranno utilizzabili, proprio grazie al fatto di essere private.

Approfondimento breve ma IMPORTANTE


Ho scritto questa pillola come se JavaScript fosse Object Oriented solo con le classi. Dopo aver preso spranghe sui denti nella ML di Python per il mio concetto sintattico / visuale della programmazione ad oggetti (istanza.methodo() o istanza.parametro o new Classe) credo sia doveroso specificare che JavaScript comunque un linguaggio Object Oriented.
function pippo(){};

sebbene sia un modo apparentemente procedurale di utilizzare JS il core del browser interpreta pippo come metodo della super classe window, window.pippo() infatti richiamer esattamente quella funzione (o quel suo metodo) come descritto nella parte relativa allo scope di questa pillola. Essendo tutto sotto window ad eccezzione delle top level functions (eval, encodeURI, escape, encodeURIComponent, alert ... e poco altro) a prescindere che si utilizzi JavaScript con le classi che non lo si utilizzi in questo modo, si sta comunque sviluppando con un linguaggio Object Oriented. alert((1).toString()) ... ne l'esempio pi semplice, le "primitive" sono gi classi e tutti i derivati di window sono gi oggetti.

Digging a real OO class system with jQuery


At Digg we use jQuery extensively, but it doesn't offer a much in the way of Object-Oriented JavaScript. Existing OOJS libraries weren't a good fit -- often trying to shoehorn traditional OOP patterns in where they don't quite fit -- so we rolled our own. Class adds OOP patterns to jQuery, modeled after the original Class object in Prototype but with significantly greater capabilities -including namespacing, limited classical inheritance and more robust prototypal inheritance. How does it work?

// Hello World var MyClass = Class.create(); var mc = new MyClass(); // And now, a static class. Anytime the last argument passed to Class.create is boolean, itll serve as the static switch (defaults to false). var MyClass = Class.create(true); MyClass.namespace('foo'); // Add constructor and methods var method = { someFunc: function(){} } var MyClass = Class.create({ init: function(){ console.log('You instantiated a Class!'); }, myFunc: function(){}, myProp: 'foo' }, method); var foo = new MyClass(); foo.someFunc(); // Namespaces var MyClass = Class.create(true); //this... MyClass.namespace('bar'); //...is the same as this $.extend(MyClass, { bar: Class.create(true); });

Building an Object-Oriented jQuery Plugin


So you've been using jQuery as your Javascript framework and now you need to write a plugin. If you come from an ObjectOriented background like me, you may feel that jQuery's plugins leave a lot to be desired. The basic formula to create a jQuery plugin is to extend the plugin namespace with a single method:
#myplugin.js

jQuery.fn.myplugin = function() { // Do some cool stuff here }

While that seems all fine and dandy for simple plugins, you may need to create more robust plugins that do many things, often in a non-linear fashion. Some plugins get around this by adding tons of methods to jQuery's plugin namespace.
$('#test').plugin(); $('#test').pluginAdd('stuff'); $('#test').pluginRemove('other stuff'); $('#test').pluginDoSomethingCool();

I personally don't like that approach because it pollutes the jQuery plugin namespace with lots of methods. I personally like to stick to just one plugin method per plugin. Other plugins use the first parameter of the plugin to call methods:
$('#test').plugin(); $('#test').plugin('add', 'stuff'); $('#test').plugin('remove', 'other stuff'); $('#test').plugin('doSomethingCool');

I think this approach is a little awkward, especially if the plugin accepts an options object the first time it is created. This approachs means you would have to either write a switch of all the methods you want to expose, or blindly accept any string as a method name. To get around these hurdles, I've created a basic template for jQuery plugins that provides access to an Object-Oriented interface if needed while still maintaining jQuery's simplicity of a single method in the plugin namespace. The first thing you need to do is wrap all your plugin code in an anonymous function. This will help keep things nice and tidy without creating global variables.
#myplugin.js

(function($){ // Your plugin code goes here })(jQuery);

Next, create your plugin as a class, where the first parameter is a single DOM element.
#myplugin.js

(function($){ var MyPlugin = function(element) { var elem = $(element); var obj = this;

// Public method this.publicMethod = function() { console.log('publicMethod() called!'); }; }; })(jQuery);

To make your new object-oriented class available as a jQuery plugin, write a simple wrapper function in the plugin namespace:
#myplugin.js

(function($){ var MyPlugin = function(element) { var elem = $(element); var obj = this;

// Public method this.publicMethod = function() { console.log('publicMethod() called!'); }; };

$.fn.myplugin = function() { return this.each(function() { var myplugin = new MyPlugin(this); }); }; })(jQuery);

Now, when you call $(element).myplugin(), the jQuery plugin instantiates an instance of MyPlugin, passing the element as the first argument. But now there's a problem of how to get the object "myplugin" once it's been created. For this, I usually store the object in the elements data. This provides easy access to the object while allowing you to prevent accidental double instantiation in the event that the plugin was called again on the same element.
#myplugin.js

(function($){ var MyPlugin = function(element) { var elem = $(element); var obj = this;

// Public method this.publicMethod = function() { console.log('publicMethod() called!'); }; };

$.fn.myplugin = function() { return this.each(function() { var element = $(this);

// Return early if this element already has a plugin instance if (element.data('myplugin')) return;

var myplugin = new MyPlugin(this);

// Store plugin object in this element's data element.data('myplugin', myplugin); }); }; })(jQuery);

Now you have easy access to the object should you need to run methods on it.
$('#test').myplugin(); var myplugin = $('#test').data('myplugin'); myplugin.publicMethod(); // prints "publicMethod() called!" to console

If you need to get fancy and add options parameter or other required parameters, just pass them from the jQuery plugin to your plugin's constructor:
#myplugin.js

(function($){ var MyPlugin = function(element, options) { var elem = $(element); var obj = this;

// Merge options with defaults var settings = $.extend({ param: 'defaultValue' }, options || {});

// Public method this.publicMethod = function() { console.log('publicMethod() called!'); }; };

$.fn.myplugin = function(options) { return this.each(function() { var element = $(this);

// Return early if this element already has a plugin instance if (element.data('myplugin')) return;

// pass options to plugin constructor var myplugin = new MyPlugin(this, options);

// Store plugin object in this element's data element.data('myplugin', myplugin); }); }; })(jQuery);

You may also want to expose some of your object's methods while keeping others private. To make a private method, create a local function within your object using the var keyword:
#myplugin.js

(function($){ var MyPlugin = function(element, options) { var elem = $(element); var obj = this; var settings = $.extend({ param: 'defaultValue' }, options || {});

// Public method - can be called from client code this.publicMethod = function() { console.log('public method called!'); };

// Private method - can only be called from within this object var privateMethod = function() { console.log('private method called!'); }; };

$.fn.myplugin = function(options) { return this.each(function() { var element = $(this);

// Return early if this element already has a plugin instance if (element.data('myplugin')) return;

// pass options to plugin constructor var myplugin = new MyPlugin(this, options);

// Store plugin object in this element's data element.data('myplugin', myplugin); }); }; })(jQuery);

To see an example of a plugin I wrote that uses this template, check out my Tagger plugin.