Orientato agli oggetti fino dal nucleo, JavaScript offre funzionalità OOP potenti e flessibili. Questo articolo inizia con una introduzione alla programmazione orientata agli oggetti, quindi presenta il modello di oggetti JavaScript e, infine, dimostra concetti della programmazione orientata agli oggetti in JavaScript.
Riesame su JavaScript
Se non ti senti sicuro su concetti JavaScript come variabili, tipi, funzioni e ambito di applicazione, puoi leggere su questi argomenti in A re-introduction to JavaScript. Puoi inoltre consultare la Core JavaScript 1.5 Guide.
Programmazione object-oriented
La programmazione orientata agli oggetti è un paradigma di programmazione che usa astrazione (abstraction) per creare modelli basati sul mondo reale. Utilizza diverse tecniche di paradigmi esistenti, tra cui la modularità (modularity), il polimorfismo (polymorphism) e l'incapsulamento (encapsulation). Oggi molti linguaggi di programmazione (come Java, JavaScript, C #, C ++, Python, PHP, Ruby e Objective-C) supportano la programmazione orientata agli oggetti (OOP).
OOP ha una visione del software come un insieme di oggetti cooperanti, piuttosto che un insieme di funzioni o semplicemente un elenco di istruzioni per il computer (come è nella visione tradizionale). In OOP, ogni oggetto è in grado di ricevere messaggi, elaborare dati, e inviare messaggi ad altri oggetti. Ogni oggetto può essere visto come una piccola macchina indipendente con ruolo o responsabilità distinti.
OOP promuove una maggiore flessibilità e mantenibilità nella programmazione, ed è molto popolare in ingegneria del software su larga scala. Poiché OOP sottolinea con forza la modularità, il codice object-oriented è più semplice da sviluppare e più facile da capire in seguito. Il codice object-oriented promuove un'analisi, codifica, e comprensione di situazioni e procedure complesse più diretta che con metodi di programmazione a minor modularità.1
Terminologia
- Namespace
- Un contenitore che consente agli sviluppatori di includere tutte le funzionalità sotto un nome unico, specifico per l'applicazione.
- Class
- Definisce le caratteristiche dell'oggetto. Una classe è una definizione del modello, delle proprietà e dei metodi di un oggetto.
- Object
- Istanza di una classe.
- Property
- Una caratteristica di un oggetto, come un colore.
- Method
- Una capacità di un oggetto, come ad esempio cammina. È una procedura o funzione associata a una classe.
- Constructor
- Un metodo invocato nel momento in cui l'oggetto viene istanziato. Di solito ha lo stesso nome della classe che lo contiene.
- Inheritance
- Una classe può ereditare caratteristiche da un'altra classe.
- Encapsulation
- Una modalità di raggruppamento di dati e metodi che ne fanno uso.
- Abstraction
- L'insieme del complesso di eredità, metodi e proprietà di un oggetto deve rispecchiare adeguatamente un modello reale.
- Polymorphism
- Poly significa "molti" e morfismo significa "forme". Classi diverse potrebbero definire lo stesso metodo o proprietà.
Per una descrizione più ampia della programmazione orientata agli oggetti, vedi Object-oriented programming su Wikipedia.
Programmazione prototype-based
La programmazione prototype-based (basata sul prototipo) è un modello OOP che non utilizza le classi, ma piuttosto prima completa il comportamento di qualsiasi classe e poi riutilizza (equivalente all'ereditarietà nei linguaggi basati su classi) arricchendo (o sviluppando) oggetti prototipo esistenti. (Chiamata anche senza classi (classless), orientata al prototipo (prototype-oriented), o programmazione basata su istanza (instance-based).)
L'esempio originale (e più canonico) di un linguaggio basato sui prototipi è Self sviluppato da David Ungar e Randall Smith. Tuttavia lo stile di programmazione senza classi diventa sempre più popolare negli ultimi tempi, ed è stato adottato per i linguaggi di programmazione come JavaScript, Cecil, NewtonScript, Io, MOO, REBOL, Kevo, Squeak (quando si utilizza il framework Viewer per manipolare componenti Morphic), e molti altri.1
Programmazione Object Oriented in JavaScript
Namespace
Un namespace è un contenitore che consente agli sviluppatori di includere tutte le funzionalità sotto un nome unico, specifico per l'applicazione. In JavaScript un namespace è solo un altro oggetto che contiene metodi, proprietà e oggetti.
Nota: È importante notare che in JavaScript non c'è alcuna differenza a livello di linguaggio tra oggetti regolari e namespace. Questo differenzia da molti altri linguaggi orientati agli oggetti e può essere fonte di confusione per i nuovi programmatori JavaScript.
L'idea alla base della creazione di un namespace in JavaScript è semplice: creare un oggetto globale, e tutte le variabili, i metodi e le funzioni diventano proprietà di tale oggetto. L'uso dei namespace riduce anche il rischio di conflitti tra nomi in un'applicazione, poiché gli oggetti di ciascuna applicazione sono proprietà di un oggetto globale definito dall'applicazione.
Creiamo un oggetto globale chiamato MYAPP:
// global namespace
var MYAPP = MYAPP || {};
Nel codice di esempio di sopra prima viene testato se MYAPP è già definito (sia nello stesso o in un altro file). Se sì, allora viene utilizzato l'oggetto globale MYAPP già esistente, altrimenti viene creato un oggetto vuoto chiamato MYAPP che incapsula metodi, funzioni, variabili e oggetti.
Possaimo anche creare sotto-namespace (tieni presente che l'oggetto globale deve essere definito prima):
// sub namespace
MYAPP.event = {};
La seguente è la sintassi del codice per la creazione di un namespace e l'aggiunta di variabili, funzioni, e un metodo:
// Create container called MYAPP.commonMethod for common method and properties
MYAPP.commonMethod = {
regExForName: "", // define regex for name validation
regExForPhone: "", // define regex for phone no validation
validateName: function(name){
// Do something with name, you can access regExForName variable
// using "this.regExForName"
},
validatePhoneNo: function(phoneNo){
// do something with phone number
}
}
// Object together with the method declarations
MYAPP.event = {
addListener: function(el, type, fn) {
// code stuff
},
removeListener: function(el, type, fn) {
// code stuff
},
getEvent: function(e) {
// code stuff
}
// Can add another method and properties
}
// Syntax for Using addListener method:
MYAPP.event.addListener("yourel", "type", callback);
Oggetti incorporati (built-in) standard
JavaScript ha diversi oggetti inclusi nel suo nucleo, per esempio ci sono oggetti come Math, Object, Array e String. L'esempio seguente mostra come utilizzare l'oggetto Math per ottenere un numero casuale utilizzando il suo metodo random()
.
console.log(Math.random());
console.log()
sia definita a livello globale. La funzione console.log()
in realtà non è parte di JavaScript in sé, ma molti browser la implementano per aiutare il debug.Vedi JavaScript Reference: Standard built-in objects for una lista degli oggetti di nucleo in JavaScript.
Ogni oggetto in JavaScript è una instanza dell'oggetto Object
e perciò ne eredita tutte le sue proprietà e metodi.
Oggetti utente
La classe
JavaScript è un linguaggio basato sui prototipi e non contiene alcuna dichiarazione di classe, come invece si trova in C ++ o Java. Questo a volte è fonte di confusione per i programmatori abituati ai linguaggi con una dichiarazione class
. JavaScript utilizza le funzioni come costruttori per le classi. Definire una classe è facile come definire una funzione. Nell'esempio sottostante si definisce una nuova classe chiamata Person con un costruttore vuoto.
var Person = function () {};
L'oggetto (istanza di classe)
Per creare una nuova istanza di un oggetto obj
utilizziamo l'istruzione new obj
assegnando il risultato (che è di tipo obj
) ad una variabile, per poi accedervi successivamente.
Nell'esempio precedente abbiamo definito una classe chiamata Person
. In quello seguente creiamo due istanze (person1
e person2
).
var person1 = new Person();
var person2 = new Person();
Object.create()
per un ulteriore nuovo metodo d'istanza che crea una istanza non inizializzata.Il costruttore
Il costruttore viene chiamato quando si instanzia un oggetto (il momento in cui si crea l'istanza dell'oggetto). Il costruttore è un metodo della classe. In JavaScript la funzione funge da costruttore dell'oggetto, pertanto non è necessario definire esplicitamente un metodo costruttore. Ogni azione dichiarata nella classe viene eseguita al momento della creazione di un'istanza.
Il costruttore viene utilizzato per impostare le proprietà dell'oggetto o per chiamare i metodi che preparano l'oggetto per il suo uso. L'aggiunta di metodi di classe e le loro definizioni si effettua utilizzando una sintassi diversa descritta più avanti in questo articolo.
Nell'esempio seguente il costruttore della classe Person
registra un messaggio quando viene istanziato un oggetto Person
.
var Person = function () {
console.log('instance created');
};
var person1 = new Person();
var person2 = new Person();
La proprietà (attributo di oggetto)
Le proprietà sono variabili contenute nella classe; ogni istanza dell'oggetto ha queste proprietà. Le proprietà sono impostate nel costruttore (funzione) della classe in modo che siano creati su ogni istanza.
La parola chiave this
, che si riferisce all'oggetto corrente, consente di lavorare con le proprietà all'interno della classe. L'accesso (in lettura o scrittura) ad una proprietà al di fuori della classe è fatto con la sintassi: NomeIstanza.Proprietà
, proprio come in C ++, Java, e molti altri linguaggi. (All'interno della classe la sintassi this.Proprietà
è utilizzata per ottenere o impostare il valore della proprietà.)
Nell'esempio seguente, definiamo al momento della creazione la proprietà firstName
per la classe Person
:
var Person = function (firstName) {
this.firstName = firstName;
console.log('Person instantiated');
};
var person1 = new Person('Alice');
var person2 = new Person('Bob');
// Show the firstName properties of the objects
console.log('person1 is ' + person1.firstName); // logs "person1 is Alice"
console.log('person2 is ' + person2.firstName); // logs "person2 is Bob"
I metodi
I metodi sono funzioni (e definiti come funzioni), ma per il resto seguono la stessa logica delle proprietà. Chiamare un metodo è simile all'accesso a una proprietà, ma si aggiunge () alla fine del nome del metodo, eventualmente con argomenti. Per definire un metodo va assegnata una funzione a una proprietà della proprietà prototype
della classe. In seguito sarà possibile chiamare il metodo sull'oggetto utilizzando lo stesso nome assegnato alla funzione.
Nell'esempio seguente definiamo e usiamo il metodo sayHello()
per la classe Person
.
var Person = function (firstName) {
this.firstName = firstName;
};
Person.prototype.sayHello = function() {
console.log("Hello, I'm " + this.firstName);
};
var person1 = new Person("Alice");
var person2 = new Person("Bob");
// call the Person sayHello method.
person1.sayHello(); // logs "Hello, I'm Alice"
person2.sayHello(); // logs "Hello, I'm Bob"
In JavaScript i metodi sono oggetti funzione regolarmente associati a un oggetto come una proprietà, il che significa che è possibile richiamare i metodi "fuori dal contesto". Si consideri il seguente codice di esempio:
var Person = function (firstName) {
this.firstName = firstName;
};
Person.prototype.sayHello = function() {
console.log("Hello, I'm " + this.firstName);
};
var person1 = new Person("Alice");
var person2 = new Person("Bob");
var helloFunction = person1.sayHello;
// logs "Hello, I'm Alice"
person1.sayHello();
// logs "Hello, I'm Bob"
person2.sayHello();
// logs "Hello, I'm undefined" (or fails
// with a TypeError in strict mode)
helloFunction();
// logs true
console.log(helloFunction === person1.sayHello);
// logs true
console.log(helloFunction === Person.prototype.sayHello);
// logs "Hello, I'm Alice"
helloFunction.call(person1);
Come dimostra questo esempio tutti i riferimenti alla funzione sayHello
— quello su person1
, su Person.prototype
, nella variabile helloFunction
, ecc.— si riferiscono tutti alla stessa funzione. Il valore di this
nel corso di una chiamata alla funzione dipende da come noi lo chiamiamo. Più comunemente, quando chiamiamo this
in un'espressione dove abbiamo ottenuto la funzione da una proprietà di oggetto — person1.sayHello()
— this
è riferito all'oggetto da cui abbiamo ottenuto la funzione (person1
), questa è la ragione per cui person1.sayHello()
usa il nome "Alice" e person2.sayHello()
usa il nome "Bob". Ma se lo chiamiamo in altri modi, this
è impostato in modo diverso: chiamare this
da una variabile— helloFunction()
— imposta this
come riferimento all'oggetto globale (window
, sui browser). Dal momento che l'oggetto globale (probabilmente) non dispone di una proprietà firstName
otteniamo "Hello, I'm undefined". (Questo accade in modalità blanda (loose mode); sarebbe stato diverso [un errore] in strict mode, ma per evitare confusione qui non entreremo nei dettagli). Oppure si può impostare this
esplicitamente utilizzando Function#call
(o Function#apply
), come illustrato alla fine dell'esempio.
Eredità
L'ereditarietà è un modo per creare una classe come una versione specializzata di una o più classi (JavaScript supporta solo l'ereditarietà singola). La classe specializzata viene comunemente chiamata figlio, e l'altra classe viene comunemente chiamato padre. In JavaScript si esegue questa operazione assegnando un'istanza della classe padre alla classe figlio, e poi specializzandola. Nel browser moderni è anche possibile utilizzare Object.create per implementare l'ereditarietà.
Nota: JavaScript non rileva prototype.constructor
della classe figlio (vedi Object.prototype), quindi devi farlo manualmente. Vedi la domanda "Why is it necessary to set the prototype constructor?" su Stackoverflow.
Nell'esempio seguente definiamo la classe Student
come classe figlio di Person
. Quindi ridefiniamo il metodo sayHello()
e aggiungiamo il metodo sayGoodBye()
.
// Define the Person constructor
var Person = function(firstName) {
this.firstName = firstName;
};
// Add a couple of methods to Person.prototype
Person.prototype.walk = function(){
console.log("I am walking!");
};
Person.prototype.sayHello = function(){
console.log("Hello, I'm " + this.firstName);
};
// Define the Student constructor
function Student(firstName, subject) {
// Call the parent constructor, making sure (using Function#call)
// that "this" is set correctly during the call
Person.call(this, firstName);
// Initialize our Student-specific properties
this.subject = subject;
};
// Create a Student.prototype object that inherits from Person.prototype.
// Note: A common error here is to use "new Person()" to create the
// Student.prototype. That's incorrect for several reasons, not least
// that we don't have anything to give Person for the "firstName"
// argument. The correct place to call Person is above, where we call
// it from Student.
Student.prototype = Object.create(Person.prototype); // See note below
// Set the "constructor" property to refer to Student
Student.prototype.constructor = Student;
// Replace the "sayHello" method
Student.prototype.sayHello = function(){
console.log("Hello, I'm " + this.firstName + ". I'm studying "
+ this.subject + ".");
};
// Add a "sayGoodBye" method
Student.prototype.sayGoodBye = function(){
console.log("Goodbye!");
};
// Example usage:
var student1 = new Student("Janet", "Applied Physics");
student1.sayHello(); // "Hello, I'm Janet. I'm studying Applied Physics."
student1.walk(); // "I am walking!"
student1.sayGoodBye(); // "Goodbye!"
// Check that instanceof works correctly
console.log(student1 instanceof Person); // true
console.log(student1 instanceof Student); // true
Per quanto riguarda la linea Student.prototype = Object.create(Person.prototype);
: in vecchi interpreti JavaScript senza Object.create
, si può utilizzare un "polyfill" (anche detto "shim", vedi l'articolo collegato), oppure utilizzare una funzione che produce il medesimo risultato, come:
function createObject(proto) {
function ctor() { }
ctor.prototype = proto;
return new ctor();
}
// Usage:
Student.prototype = createObject(Person.prototype);
Essere sicuri che this
punti alla cosa giusta a prescindere da come l'oggetto sia istanziato può essere difficile. Tuttavia c'e una tecnica semplice per renderlo più facile.
var Person = function(firstName) {
if (this instanceof Person) {
this.firstName = firstName;
} else {
return new Person(firstName);
}
}
Incapsulamento
Nell'esempio precedente Student
non ha bisogno di sapere come sia realizzato il metodo walk()
della classe Person
, ma può comunque utilizzarlo; la classe Student
non ha bisogno di definire in modo esplicito questo metodo se non vogliamo cambiarlo. Questo è chiamato incapsulamento, per cui ogni classe impacchetta dati e metodi in una singola unità.
Il nascondere informazioni (information hiding) è una caratteristica comune ad altre linguaggi, spesso in forma di metodi / proprietà private e protette. Anche se si potrebbe simulare qualcosa di simile in JavaScript, questo non è un requisito per fare programmazione Object Oriented.2
Astrazione
L'astrazione è un meccanismo che permette di modellare la parte corrente del problema, sia con eredità (specializzazione) o la composizione. JavaScript ottiene la specializzazione per eredità, e la composizione permettendo che istanze di classe siano valori di attributi di altri oggetti.
La classe Function di Javascript eredita dalla classe Object (questo dimostra la specializzazione del modello) e la proprietà Function.prototype è un'istanza di oggetto (ciò dimostra la composizione).
The JavaScript Function class inherits from the Object class (this demonstrates specialization of the model) and the Function.prototype property is an instance of Object (this demonstrates composition).
var foo = function () {};
// logs "foo is a Function: true"
console.log('foo is a Function: ' + (foo instanceof Function));
// logs "foo.prototype is an Object: true"
console.log('foo.prototype is an Object: ' + (foo.prototype instanceof Object));
Polimorfismo
Così come tutti i metodi e le proprietà sono definite all'interno della proprietà prototype, classi diverse possono definire metodi con lo stesso nome; i metodi sono nell'ambito della classe in cui sono definiti, a meno che le due classi siano in un rapporto padre-figlio (cioè una eredita dall'altra in una catena di ereditarietà).
Note
Le tecniche presentate in questo articolo per attuare la programmazione orientata agli oggetti non sono le uniche che possono essere utilizzate in JavaScript, che è molto flessibile in termini di come la programmazione orientata agli oggetti possa essere eseguita.
Allo stesso modo, le tecniche qui indicate non usano alcuna violazione di linguaggio, né imitano implementazioni di teorie di oggetti di altri linguaggi.
Ci sono altre tecniche che rendono la programmazione orientata agli oggetti in JavaScript ancora più avanzata, ma sono oltre la portata di questo articolo introduttivo.
Riferimenti
- [] Mozilla. "Core JavaScript 1.5 Guide", https://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Guide
- [] wikipedia. "Object-oriented programming", https://en.wikipedia.org/wiki/Object-...ed_programming
- Author(s): Fernando Trasviña <f_trasvina at hotmail dot com>
- Copyright Information: © 1998-2005 by individual mozilla.org contributors; content available under a Creative Commons license