ES6: l’avvento delle classi in Javascript (o quasi).

L’ultima incarnazione di Javascript (ES6*) ha portato diverse novità, tra cui l’introduzione delle Classi nella creazione degli oggetti, soppiantando la sintassi che definiva la “funzione costruttrice” che eravamo abituati ad usare. Ma come funzionano esattamente le “classi” in Javascript?

Diciamo subito le cose come stanno: più che di Classi sarebbe giusto chiamarle “Pseudo Classi” perché, ad essere sinceri, l’introduzione della keyword Class in Javascript è più un’operazione cosmetica che di sostanza; come vedremo, non si tratta infatti di Classi in senso stretto, come quelle che incontriamo in altri linguaggi di programmazione object-oriented (es. JAVA), Javascript rimane di fatto basato sui Prototipi (prototype-based), non sulle Classi (class-based), ma tant’è. Vediamo bene di cosa si tratta.

Prima di ES6*, volendo creare un oggetto, avremmo scritto una “Funzione Costruttrice” ed assegnato proprietà e metodi. Le prime, sotto forma di semplici coppie di variabili e valori; i secondi, definendo all’interno della Funzione Costruttrice altre funzioni, come fossero anch’esse proprietà abbinate a semplici variabili; oppure – meglio – ricorrendo al “Prototipo” dell’oggetto stesso. Meglio perché, così facendo, avremmo alleggerito la struttura di ogni singola istanza dell’oggetto, dal fardello di “portarsi dietro” i metodi che hanno in comune, delegando invece al Prototipo dell’oggetto tale compito, fungendo da riferimento univoco per tutte le istanze create su tale modello (attraverso il sistema di eredità prototipica).

// Prima di ES6 // utilizzando la Constructor Function

function Particle(x_, y_, w_, h_){
  this.x = x_;
  this.y = y_;
  this.w = w_;
  this.h = h_;
  /* // definendo il metodo all'interno dell'oggetto, come proprietà
  this.show = function(){
   rect(this.x, this.y, this.h, this.w);
  }
  */
}

/* Delegando al prototipo del mio oggetto la gestione della funzione Show una volta per tutte */

Particle.prototype.show = function(){
 rect(this.x, this.y, this.h, this.w);
 }

var p = new Particle(50, 50, 100, 50);
Se adesso, nella Console del mio Browser, digitassi “p“, potrei vedere la struttura del mio oggetto (provate per credere).

 

p
> Particle {x: 50, y: 50, w: 100, h: 50}
   h : 50
   w : 100
   x : 50
   y : 50
 > __proto__ :
     show : f()
     constructor : f Particle(x_, y_, w_, h_)
     __proto__ : Object
Con ES6 le cose sono leggermente cambiate, soprattutto a livello di sintassi: la funzione “speciale” deputata alla costruzione degli oggetti è stata mandata in pensione, in favore dell’introduzione della parola chiave “class“, ad indicare un blocco di codice che servirà da template per la creazione di un determinato tipo di oggetti,  seguita dal nome da noi scelto- con iniziale maiuscola -. All’interno di tale blocco è prevista la funzione “constructor“, deputata all’inizializzazione delle proprietà dell’oggetto, a seguire, i metodi che, dietro le quinte verranno automaticamente attribuiti al prototipo del nostro oggetto. Aggiorniamo l’esempio visto in precedenza con la nuova sintassi or ora descritta:

 

/* Costruiamo un oggetto "particella" utilizzando la sintassi introdotta da ES6 */

class Particle {
 constructor (x_, y_, w_, h_) { 
  this.x = x_;
  this.y = y_;
  this.w = w_;
  this.h = h_;
 }
 show() { 
  rect(this.x, this.y, this.h, this.w);
 }
 move() { 
  this.x = this.x + 1;
  this.y = this.y + 1;
 }
}

let p = new Particle(50, 50, 100, 50);
Ecco fatto, abbiamo appena creato un oggetto attraverso la nuova sintassi, incluso l’utilizzo della nuova keyword “let” che in ES6 ha sostituito la cara, vecchia, amata “var” (ma questa è un’altra storia). Tutto qua? Possiamo mettere a dormire il computer ed andarci a prendere una boccata d’aria? Nì. In verità, per completezza, ci sono alcune cose da aggiungere; quindi, se volete, fate pure una pausa, ma poi vi suggerisco di finire di leggere questo post.

 

Questa foto non ha assolutamente niente a che vedere con l’argomento di questo post. Interpretatela come una pausa pubblicitaria.

 

I fan di altri linguaggi di programmazione class-based, come Java e C++, potrebbero a questo punto esultare ma, come accennavo all’inizio di questo post, non è tutto oro quello che luccica. Ad esempio potremmo notare come Class Keyword in Javascript non supporta Membri Privati; è infatti sempre possibile accedere dall’esterno ai metodi definiti all’interno di una determinata classe e modificarli a piacimento (o piuttosto per errore). La ragione per cui tale supporto non è stato previsto nella recente release di ECMAScript è molto semplice: in realtà ES6 non ha introdotto delle vere e proprie Classi ma piuttosto una finzione sintattica, come possiamo facilmente dimostrare semplicemente interrogando la Console del Browser in merito al codice appena scritto:

 

typeof Particle 
"function"
Ebbene sì, se volete potete pure chiamarla Classe ma, quella da noi definita rimane, sotto al cofano di JavaScript pur sempre una funzione.

 

Particle.prototype.show 
ƒ (){
 rect(this.x, this.y, this.h, this.w);
 }
La sostanza non è cambiata rispetto a prima: rimane la struttura fondante basata sui Prototipi ed il principio di ereditarietà ad essi applicata.
Allora perché tutto questo sbattimento direte voi? Principalmente proprio per rendere JavaScript più appetibile a programmatori che vengono da altri linguaggi realmente class-based.

 

*ES6 – acronimo di ECMAScript 6 – è stata rilasciata a giugno 2015, e oggi vede un livello di implementazione da parte dei produttori dei Browser sufficientemente avanzato da spingere gli sviluppatori ad abbracciarne l’adozione nel proprio codice.

 

A proposito degli oggetti in Javascript

In JavaScript, tutto o quasi ruota intorno agli oggetti; capire come funzionano è perciò essenziale. Libri, corsi e tutorial, spesso liquidano la questione in modo sbrigativo ma la parte che affiora in superficie è solo la piccola punta di un grande e grosso iceberg. Questo post è solo una boa, che segnala il pericolo: attenzione, iceberg a dritta!

Javascript è un linguaggio di programmazione object-oriented, fondato cioè su oggetti che potremmo definire come strutture di dati, costruite sulla base di template che ne definiscono proprietà e funzionalità. Allo stesso modo in cui possiamo definire gli oggetti del mondo reale, come ad esempio la mia auto: una utilitaria, del 2015, di colore bianco, 5 porte, motore ibrido, marca Toyota (proprietà). Capace di accelerare, frenare, sterzare, trasportando oggetti e persone da un luogo all’altro (funzioni).

Javascript ci mette a disposizione tutta una serie di oggetti di uso comune, preconfezionati, accessoriati di tutto punto con funzionalità e proprietà utilizzabili e definibili a piacimento; come ad esempio gli Array.

Gli array sono oggetti pensati per creare liste di “cose”: dati primitivi come numeri, e/o dati complessi come altri oggetti.
In javascript un array è un contenitore molto generico e flessibile, che può essere riempito e svuotato a piacimento, con qualsiasi tipo di dato (diversamente da ciò che accade, ad esempio, in Java).

Tipicamente, per utilizzare un oggetto, ne definiamo/creiamo un’istanza attraverso un operatore speciale, l’operatore “new” che precede la particolare “funzione costruttrice” designata all’assemblaggio dell’ istanza dell’oggetto in questione, sulla base di una sorta di template predefinito.

var myArray = new Array(); // object constructor notation rarely used

A dire il vero, in pratica, nel 99% dei casi, vedrete gli array creati in quest’altro modo:

var myArray = []; // literal notation

La sostanza comunque non cambia. Adesso abbiamo il nostro bell’Array (a cui fare riferimento attraverso la variabile myArray), che possiamo popolare ad esempio con una lista di 10 numeri casuali.

populateArray(myArray, 10); 

var populateArray = function(whichArray, arraySpots) {
 for (var i = 0; i < arraySpots; i++) {
 whichArray[i] = Math.floor(Math.random() * 100);
 }
}

// avrei anche potuto popolare l'array quando l'ho creato es. myArray = [15, 12, 1, 56, 89, 99, ....];

!! Attenzione. Al contrario di ciò che avviene per i dati così detti “primitivi” come ad esempio numeri. La variabile che assegnamo all’oggetto (in questo caso myArray), non contiene il dato, fa riferimento ad esso. Si veda l’esempio seguente.

var myPrimitiveDataVariable = 2;
var myPrimitiveDataVariable2 = myPrimitiveDataVariable;
myPrimitiveDataVariable = 5;
//console.log(myPrimitiveDataVariable2); // ancora 2

//-----------------------

var myArrayObject = [2];
var myArrayObject2 = myArrayObject;
myArrayObject[0] = 4;
//console.log(myArrayObject2); // 4


Come già detto, gli oggetti hanno proprietà e metodi, ovvero variabili e funzioni. Alcune funzioni ritornano un valore, altre semplicemente svolgono dei compiti. Ad esempio posso accedere alla proprietà nativa “length“, di cui è “dotato di serie” ogni Array per conoscere da quanti elementi è composta la mia lista.

myArray = [];
console.log(myArray.length); // 0

Oppure posso invocare uno dei metodi nativi come “sort()“, ideato per riordinare l’array.

myArray = [15, 12, 1, 56, 89, 99];
console.log(myArray.sort()); // [1, 12, 15, 56, 89, 99]

Oggetti preconfezionati a parte, la vera potenza creativa di un linguaggio object-oriented come JavaScript si sprigiona attraverso la possibilità per il programmatore di creare i propri oggetti personalizzati.

In questo caso però non basta dichiarare l’oggetto attraverso l’operatore “new” a precedere la funzione costruttrice. Quest’ultima infatti non esiste ancora. È necessario definirla. Alternativamente, possiamo creare il nostro oggetto utilizzando la notazione letterale ma ad un costo: l’impossibilità di riutilizzare tale codice per creare altre istanze dell’oggetto. In pratica la notazione letterale si presta alla creazione di pezzi unici, che forse avranno un valore sul mercato dell’arte, ma che in programmazione non hanno un gran seguito. Partiamo comunque da qua: dicevamo all’inizio della mia auto… Costruiamo un oggetto in grado di rappresentarla.

var myCar = {};

Notate la somiglianza con la notazione letterale vista poco fa per gli array? Cambiano le parentesi, da quadre a graffe ma non solo quelle! Potreste magari pensare: “Ok, cominciamo a buttar roba dentro il mio oggetto! Dunque… myCar{0} = …. 
Assolutamente no. State definendo un oggetto con delle proprietà specifiche, non una lista ordinata di cose! Per quella andava benissimo l’oggetto preconfezionato Array. Per definire le proprietà della mia auto dovrò utilizzare una sintassi che mi permette di definire nome e valore di ciascuna proprietà (key-value pairs).

A tal proposito, una prima opzione prevede una sintassi che utilizza la parentesi quadra:

myCar["type"] = "furgoncino";

Questa sintassi è però quella che presumibilmente utilizzerete di meno; viene infatti usata in casi specifici ove diventa una scelta obbligatoria.
Tali casi includono: nomi di proprietà composti da più parole, nomi di proprietà numerici, nomi di proprietà costituiti da una variabile (in quest’ultimo caso vanno però omesse le virgolette).

L’altra sintassi, quella più diffusa, è la sintassi del punto, che vedete a seguire. Entrambe le sintassi possono non solo essere utilizzare per creare una proprietà, ma anche semplicemente per cambiar il valore di una proprietà già esistente, con lo stesso nome.

myCar.type = "utilitaria";

Omettendo la parte del codice in cui assegno un valore alla proprietà, tramite l’operatore “=”, ho invece semplicemente accesso al valore di tale proprietà

console.log(myCar.type); // utilitaria 
// oppure
console.log(myCar["type"]); // utilitaria

Continuando con l’assemblaggio della mia automobile:

myCar.year = 2015;
myCar.brand = "Toyota";
myCar.engine = "Hybrid";
//etc.

Invece di creare l’oggetto vuoto, e riempirlo successivamente, è anche possibile, e più comune, accorpare le due fasi. Nel caso dell’Array, abbiamo visto: semplicemente scrivendo ad esempio var myArray = [1998, 1999, 2000, 2001, 2002]; Nel caso del mio oggetto, avrei potuto definire direttamente myCar in questo modo:

var myCar = {
 type: "furgoncino",
 year: 2011,
 brand: "VolksWagen",
 engine: "Diesel"
};

Sarebbe stato bello avere l’uguale invece dei due punti? Invece no, è così: ci vogliono i due punti e per di più ogni proprietà deve essere separata da una virgola, tranne l’ultima proprietà che invece non ha bisogno di alcun segno di punteggiatura, esattamente come per gli elementi di un Array.

Ho appena creato un pezzo unico. Nel caso in cui avessi una seconda auto, dovrei ricominciare a scrivere da zero il codice per un nuovo oggetto:

var myCar2 = {
 type: "utilitaria",
 year: 2015,
 brand: "Toyota",
 engine: "Hybrid"
};

Fortunatamente in famiglia non abbiamo altre auto, e quindi non devo scrivere molte altre righe di codice ma immaginate di essere un concessionario con un centinaio di auto… Vi trovereste a dover scrivere migliaia di righe di codice, malgrado la struttura sia sempre la stessa.

Abbiamo un’altra strada: definire una funzione costruttrice (!*) che ci permetterà di create tutte le istanze dell’oggetto che vogliamo, semplicemente invocandola attraverso l’operatore “new” e passando gli argomenti necessari all’assemblaggio delle nostre auto.

!* Con l’adozione di ES6 la funzione costruttrice è stata “rivoluzionata” dall’introduzione delle classi, si veda a riguardo questo post.

Facciamo finta di averla già definita è chiamiamo la funzione che ci serve per costruire le 2 auto di cui sopra (per convenzione utilizzeremo per la funzione un nome dall’iniziale maiuscola: Car):

var myCar = new Car("furgoncino", 2011, "VolksWagen", "Diesel");
var myCar2 = new Car("utilitaria", 2015, "Toyota", "Hybrid");

Ed ecco la funzione che fa al caso nostro, un’unica funzione, per tutte le auto che vogliamo:

function Car(temptype, tempyear, tempbrand, tempengine) {
 this.type = temptype;
 this.year = tempyear;
 this.brand = tempbrand;
 this.engine = tempengine;
}

A cosa si riferisca la parola chiave “this” meriterebbe un post tutto per se o, anche meglio, un’intero libro! Diciamo qua semplicemente che “this” fa le veci del nome dell’oggetto.

A parte le specifiche proprietà di ciascuna auto, tutte le auto hanno generalmente le medesime funzionalità: accelerare, frenare, sterzare etc. Tecnicamente si parla di metodi di un oggetto, in pratica si tratta di funzioni che operano su di un oggetto. Anche in questo caso non c’è un solo modo di implementarle nel codice: possiamo inserirle direttamente come una proprietà qualsiasi come di seguito.

function Car(temptype, tempyear, tempbrand, tempengine) {
 this.type = temptype;
 this.year = tempyear;
 this.brand = tempbrand;
 this.engine = tempengine;
 this.accelerate = function() {
 // do something
 console.log("accceeeleratinnnng!!!");
 };
}

Oppure, e specialmente in casi come questo, in cui le medesime funzionalità appartengono a tutte le istanze del nostro oggetto, possiamo definire il metodo per l’accelerazione abbinandola alla proprietà superiore “prototype” scrivendo il seguente codice all’esterno della funzione costruttrice Car.

Car.prototype.accelerate = function() {
 // do something
 console.log("Wroooooommmmm");
}

In entrambi i casi la chiamata per accedere a tale metodo è la medesima:

myCar.accelerate();

In qualsiasi momento ci si può appoggiare al prototipo per aggiungere sia nuove proprietà che nuovi metodi a tutti gli oggetti basati su di esso. Contrariamente a quanto si potrebbe pensare, se eseguite il codice di cui sopra, senza rimuovere il duplicato del metodo all’interno del corpo della funzione costruttrice, noterete che quest’ultimo prevale comunque sempre sul suo omonimo definito successivamente attraverso prototype. E questa non è l’unica cosa a cui prestare attenzione quando si ha a che fare con i prototipi. Conoscere meglio i prototipi ed imparare a maneggiarli con cura è tuttavia argomento per un altro post.

Spero che questa breve panoramica sul complesso ed affascinante mondo degli oggetti in JavaScript possa risultarvi utile. Nel caso in cui voleste approfondire l’argomento, vi consiglio di leggere “You Don’t Know JS: this & Object Prototypes” di Kyle Simpson.