Javascript OOP
Javascript OOP
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.
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.
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.
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
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
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
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.
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.
// 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); });
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
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;
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;
$.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;
// Return early if this element already has a plugin instance if (element.data('myplugin')) return;
// 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 || {});
// Return early if this element already has a plugin instance if (element.data('myplugin')) return;
// 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!'); }; };
// Return early if this element already has a plugin instance if (element.data('myplugin')) return;
// 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.