Frontend

Come definire una classe in JavaScript

Autore

Manuel Ricci

Le classi in JavaScript, introdotte con ES6 (ECMAScript 2015), rappresentano un'evoluzione significativa nella gestione della programmazione orientata agli oggetti (OOP) in questo linguaggio. Prima dell'introduzione delle classi, JavaScript utilizzava un modello di ereditarietà basato sui prototipi, che era potente ma spesso considerato meno intuitivo, soprattutto per sviluppatori provenienti da altri linguaggi OOP come Java o C++.

Differenze con altri linguaggi OOP

A differenza di linguaggi come Java o C++, dove la classe è un elemento fondamentale della programmazione orientata agli oggetti, JavaScript si basa su un modello di prototipi. Questo significa che le funzionalità di ereditarietà e incapsulamento vengono realizzate attraverso la clonazione di oggetti e l'uso di prototipi anziché classi.

Con l'introduzione delle classi, JavaScript offre una sintassi più in linea con quella dei linguaggi OOP tradizionali. Questo rende il linguaggio più accessibile e familiare per gli sviluppatori con esperienza in altri linguaggi OOP.

Il concetto di classe in JavaScript è spesso descritto come "syntactic sugar" rispetto al modello di ereditarietà basato sui prototipi esistente. Questo termine significa che la sintassi delle classi in JavaScript non introduce un nuovo modello di programmazione orientato agli oggetti, ma fornisce un modo più semplice e chiaro per utilizzare le capacità esistenti del linguaggio. In pratica, il codice scritto utilizzando la sintassi delle classi viene tradotto ("desugared") in funzioni e prototipi JavaScript tradizionali dal motore JavaScript durante l'esecuzione.

Semplificazione del codice OOP

Le classi in JavaScript non modificano il modello di ereditarietà sottostante ma forniscono una sintassi più chiara e concisa. Questo semplifica la scrittura e la manutenzione del codice, specialmente in progetti complessi e in team di sviluppo con background diversificati.

Compatibilità e transizione

Le classi in JavaScript sono completamente compatibili con il modello basato sui prototipi esistente. Questo significa che i progetti JavaScript esistenti possono essere gradualmente aggiornati senza rompere la compatibilità.

La compatibilità è possibile al precedente menzionato syntactic sugar. Esso infatti assicura che le classi in JavaScript siano completamente compatibili con il modello basato sui prototipi già esistente.

Significa anche che gli sviluppatori possono utilizzare sia le classi che i prototipi in modo intercambiabile all'interno dello stesso progetto, offrendo grande flessibilità e permettendo una transizione graduale verso l'uso delle classi.

Miglioramento della leggibilità e organizzazione

L'uso delle classi permette di organizzare meglio il codice, rendendolo più leggibile e facilmente comprensibile. Questo è particolarmente vantaggioso per la manutenzione del codice e per la collaborazione all'interno dei team di sviluppo.

Volentieri, approfondirò il punto sulla "Definizione di una Classe" in JavaScript per darti una visione più dettagliata e chiara di come funziona questo concetto nel linguaggio.

Definizione di una classe in JavaScript

La definizione di una classe in JavaScript si basa sull'utilizzo della parola chiave class, seguita dal nome della classe. Questa sintassi rappresenta un modo per definire una struttura di oggetti che avranno proprietà e metodi comuni. La classe agisce come un "modello" per la creazione di oggetti.

Esempio base di una classe

1class Persona {
2  constructor(nome, eta) {
3    this.nome = nome;
4    this.eta = eta;
5  }
6
7  presentati() {
8    console.log(`Mi chiamo ${this.nome} e ho ${this.eta} anni.`);
9  }
10}

Elementi chiave nella definizione di una classe

  1. Parola chiave class:

    • La dichiarazione inizia con class seguita dal nome della classe (Persona nell'esempio). Il nome della classe di solito inizia con una lettera maiuscola per convenzione.
  2. Metodo Costruttore:

    • Il constructor è un metodo speciale che viene chiamato automaticamente durante la creazione di un nuovo oggetto della classe.
    • Serve per inizializzare le proprietà dell'oggetto, come nome e eta nell'esempio.
    • this si riferisce all'oggetto appena creato e consente di assegnare valori alle sue proprietà.
  3. Definizione dei Metodi:

    • I metodi della classe, come presentati() nell'esempio, sono definiti all'interno del corpo della classe.
    • Questi metodi possono essere chiamati sugli oggetti creati dalla classe e possono operare sui dati dell'oggetto utilizzando this.
  4. Creazione di Istanze:

    • Per creare un'istanza di una classe, si utilizza la parola chiave new, seguita dal nome della classe e dai parametri richiesti dal costruttore.
    • Esempio: let persona = new Persona('Mario', 30);
  5. Scope e Incapsulamento:

    • In JavaScript, le classi supportano l'incapsulamento tramite l'uso di proprietà private, che limitano l'accesso diretto dall'esterno della classe.
  6. Sintassi Concisa:

    • Rispetto al modello basato sui prototipi, la sintassi delle classi è più concisa e leggibile, rendendo il codice più strutturato e facile da comprendere.
  7. Nota sulla Semantica:

    • È importante notare che, nonostante la parola chiave class, JavaScript utilizza ancora il modello basato sui prototipi sotto il cofano. La sintassi delle classi è un modo per rendere questo modello più accessibile e organizzato.

Metodi nelle classi JavaScript

In JavaScript, i metodi di una classe possono essere classificati in base al loro livello di accesso: pubblici, privati e protetti (convenzionalmente).

Metodi pubblici

I metodi pubblici sono accessibili da qualsiasi parte del codice dove l'oggetto della classe è disponibile. Sono la forma più comune di metodi in una classe JavaScript.

1class Persona {
2  constructor(nome) {
3    this.nome = nome;
4  }
5
6  // Metodo pubblico
7  presentati() {
8    console.log(`Mi chiamo ${this.nome}.`);
9  }
10}
11
12let persona = new Persona('Mario');
13persona.presentati(); // Output: "Mi chiamo Mario."

In questo esempio, presentati() è un metodo pubblico e può essere chiamato su qualsiasi istanza della classe Persona.

Metodi Privati

I metodi privati sono accessibili solo all'interno della classe in cui sono definiti. In JavaScript, i metodi privati vengono denotati con un prefisso #.

1class ContoBancario {
2  #saldo;
3
4  constructor(saldoIniziale) {
5    this.#saldo = saldoIniziale;
6  }
7
8  // Metodo privato
9  #aggiornaSaldo(importo) {
10    this.#saldo += importo;
11  }
12
13  deposita(importo) {
14    this.#aggiornaSaldo(importo);
15  }
16
17  visualizzaSaldo() {
18    console.log(`Saldo attuale: ${this.#saldo}`);
19  }
20}
21
22let conto = new ContoBancario(1000);
23conto.deposita(500);
24conto.visualizzaSaldo(); // Output: "Saldo attuale: 1500"
25// conto.#aggiornaSaldo(500); // Errore: #aggiornaSaldo è un metodo privato

In questo esempio, #aggiornaSaldo è un metodo privato e non può essere chiamato direttamente fuori dalla classe ContoBancario.

Metodi Protected

JavaScript non supporta direttamente i metodi protected come in altri linguaggi OOP (come Java o C++).

Tuttavia, è possibile simulare un comportamento simile usando alcune convenzioni, come l'utilizzo di un prefisso _ (anche se questo non impedisce tecnicamente l'accesso da fuori della classe).

1class Dipendente {
2  constructor(nome, stipendio) {
3    this.nome = nome;
4    this._stipendio = stipendio;
5  }
6
7  // Metodo "protected" (convenzionalmente)
8  _calcolaBonus() {
9    return this._stipendio * 0.1;
10  }
11
12  totaleCompensazione() {
13    return this._stipendio + this._calcolaBonus();
14  }
15}
16
17let dipendente = new Dipendente('Luca', 3000);
18console.log(dipendente.totaleCompensazione()); // Output: 3300
19// dipendente._calcolaBonus(); // Accessibile ma convenzionalmente "protected"

In questo esempio, _calcolaBonus è un metodo "protected". Sebbene sia tecnicamente accessibile, per convenzione, si intende che non dovrebbe essere utilizzato al di fuori della classe o delle sue sottoclassi.

Ereditarietà nelle classi JavaScript

L'ereditarietà è un principio chiave della OOP che permette a una classe di ereditare proprietà e metodi da un'altra. In JavaScript, questo viene realizzato attraverso la parola chiave extends. Una classe che eredita da un'altra è spesso chiamata "sottoclasse" o "classe derivata", mentre la classe da cui eredita è nota come "superclasse" o "classe base".

Esempio di Ereditarietà

1// Classe base
2class Persona {
3  constructor(nome) {
4    this.nome = nome;
5  }
6
7  presentati() {
8    console.log(`Mi chiamo ${this.nome}.`);
9  }
10}
11
12// Sottoclasse che estende Persona
13class Studente extends Persona {
14  constructor(nome, corso) {
15    super(nome); // Chiamata al costruttore della classe base
16    this.corso = corso;
17  }
18
19  studia() {
20    console.log(`Sto studiando ${this.corso}.`);
21  }
22}
23
24let studente = new Studente('Alice', 'Informatica');
25studente.presentati(); // Ereditato da Persona
26studente.studia(); // Metodo di Studente

In questo esempio, Studente estende Persona. La classe Studente eredita il metodo presentati() da Persona e aggiunge un suo metodo, studia().

Punti Chiave dell'ereditarietà

  1. Parola chiave extends:

    • extends è usata nella dichiarazione di una classe per creare una sottoclasse di un'altra classe.
  2. Costruttore e super:

    • Se una sottoclasse ha un metodo costruttore, deve chiamare super() prima di utilizzare this. super invoca il costruttore della classe base, permettendo alla sottoclasse di accedere alle proprietà e ai metodi ereditati.
  3. Sovrascrivere i metodi:

    • Una sottoclasse può sovrascrivere i metodi della classe base. Ciò significa che può avere la sua implementazione di un metodo che esiste già nella classe base.
  4. Catena di ereditarietà:

    • L'ereditarietà può essere a più livelli, con una catena di classi che estendono un'altra, permettendo una struttura gerarchica complessa e riutilizzabile.
  5. Polimorfismo:

    • L'ereditarietà permette il polimorfismo, dove le sottoclassi possono essere trattate come istanze della classe base, ma comportarsi in modo diverso quando i loro metodi sovrascritti vengono invocati.
  6. Accesso alle proprietà e metodi ereditati:

    • Una sottoclasse ha accesso a tutte le proprietà e i metodi pubblici e protected (ricorda che sono solo convenzionali in JavaScript, quindi base sono pubblici) della classe base, ma non ai membri privati.

Getter e setter nelle classi JavaScript

I getter e i setter sono funzioni speciali in JavaScript che permettono di controllare l'accesso alle proprietà di un oggetto. I getter sono usati per accedere ai valori delle proprietà, mentre i setter sono usati per modificare i valori delle proprietà. Questo permette di aggiungere logica aggiuntiva durante l'accesso e la modifica delle proprietà, come la validazione o la formattazione.

Esempio con getter e setter

1class Persona {
2  constructor(nome) {
3    this._nome = nome; // _nome è una convenzione per indicare una proprietà "protetta"
4  }
5
6  // Getter
7  get nome() {
8    return this._nome.charAt(0).toUpperCase() + this._nome.slice(1);
9  }
10
11  // Setter
12  set nome(nuovoNome) {
13    if (nuovoNome.length > 0) {
14      this._nome = nuovoNome;
15    } else {
16      console.log("Nome non valido!");
17    }
18  }
19}
20
21let persona = new Persona('mario');
22console.log(persona.nome); // Output: "Mario"
23
24persona.nome = ''; // Prova a impostare un nome non valido
25console.log(persona.nome); // Output: "Mario"
26persona.nome = 'luigi';
27console.log(persona.nome); // Output: "Luigi"

In questo esempio, il getter nome e il setter nome permettono un controllo maggiore sull'accesso e la modifica della proprietà _nome.

Punti Chiave sui Getter e Setter

  1. Incapsulamento:

    • Getter e setter contribuiscono all'incapsulamento, una delle caratteristiche principali della OOP. Consentono di nascondere i dettagli interni di come una proprietà è implementata e manipolata.
  2. Validazione dei dati:

    • I setter possono includere logica di validazione, per garantire che i dati siano corretti prima di essere assegnati a una proprietà.
  3. Formattazione e calcolo:

    • I getter possono essere usati per formattare i dati quando vengono letti o per calcolare valori derivati.
  4. Accesso trasparente:

    • Nonostante la logica aggiuntiva, l'accesso e la modifica delle proprietà tramite getter e setter avviene come se si trattasse di accessi diretti alla proprietà.
  5. Compatibilità con le proprietà ereditate:

    • Getter e setter possono essere definiti sia in classi base che in sottoclassi, consentendo una flessibile gestione delle proprietà ereditate.
  6. Nomi di proprietà interni:

    • È comune usare un prefisso (come _) per le proprietà interne per differenziarle dai loro getter e setter pubblici.

Volentieri, approfondirò il punto sui metodi statici nelle classi JavaScript, che sono una caratteristica importante per creare funzionalità a livello di classe piuttosto che a livello di istanza.

Metodi statici nelle classi JavaScript

I metodi statici sono funzioni associate a una classe piuttosto che alle singole istanze di quella classe. Questi metodi sono invocati direttamente sulla classe e non su un'istanza della classe. Sono utili per funzionalità che non richiedono dati da un'istanza specifica.

Esempio di Metodo Statico

1class Matematica {
2  // Metodo statico
3  static somma(a, b) {
4    return a + b;
5  }
6}
7
8let risultato = Matematica.somma(5, 10);
9console.log(risultato); // Output: 15

In questo esempio, somma è un metodo statico nella classe Matematica. Può essere chiamato direttamente tramite la classe senza dover creare un'istanza di Matematica.

Caratteristiche dei Metodi Statici

  1. Definizione:

    • I metodi statici vengono definiti utilizzando la parola chiave static. Questo li differenzia dai metodi di istanza, che sono accessibili sulle istanze della classe.
  2. Accesso:

    • I metodi statici sono accessibili direttamente sulla classe, non sulle istanze della classe. Ad esempio, Matematica.somma() è valido, mentre new Matematica().somma() non lo è.
  3. Utilizzo:

    • I metodi statici sono spesso utilizzati per creare funzioni di utilità e operazioni che non richiedono dati da un'istanza della classe.
  4. Contesto this:

    • All'interno di un metodo statico, la parola chiave this si riferisce alla classe stessa, non a un'istanza della classe. Ciò significa che non si può accedere alle proprietà dell'istanza da un metodo statico.
  5. Esempi Pratici:

    • L’oggetto Math è il classico esempio di classe che non necessita di istanziamento per poter essere utilizzato.
  6. Ereditarietà:

    • I metodi statici possono essere ereditati. Una sottoclasse può chiamare metodi statici della sua classe base.

Caricamento...

Diventiamo amici di penna? ✒️

Iscriviti alla newsletter per ricevere una mail ogni paio di settimane con le ultime novità, gli ultimi approfondimenti e gli ultimi corsi gratuiti puubblicati. Ogni tanto potrei scrivere qualcosa di commerciale, ma solo se mi autorizzi, altrimenti non ti disturberò oltre.

Se non ti piace la newsletter ti ricordo la community su Discord, dove puoi chiedere aiuto, fare domande e condividere le tue esperienze (ma soprattutto scambiare due meme con me). Ti aspetto!

Ho in previsione di mandarti una newsletter ogni due settimane e una commerciale quando capita.