30 aprile 2022

Sintassi base delle classi

Nella programmazione orientata agli oggetti una classe è un costrutto di un linguaggio di programmazione usato come modello per creare oggetti. Il modello comprende attributi e metodi che saranno condivisi da tutti gli oggetti creati (istanze) a partire dalla classe. Un “oggetto” è, di fatto, l’istanza di una classe.

Wikipedia

In pratica, spesso abbiamo bisogno di creare più oggetti dello stesso tipo, come utenti, beni o altro.

Come già sappiamo dal capitolo Costruttore, operatore "new", new function ci può aiutare in questo.

Ma nel JavaScript moderno c’è un costrutto “class” più avanzato, che introduce nuove possibilità molto utili per la programmazione ad oggetti.

La sintassi di “class”

La sintassi base è:

class MyClass {
  // metodi della classe
  constructor() { ... }
  method1() { ... }
  method2() { ... }
  method3() { ... }
  ...
}

new MyClass() creerà un nuovo oggetto con tutti i metodi presenti nella classe.

Il metodo constructor() viene chiamato automaticamente da new, dunque possiamo usarlo per inizializzare l’oggetto.

Per esempio:

class User {

  constructor(name) {
    this.name = name;
  }

  sayHi() {
    alert(this.name);
  }

}

// Utilizzo:
let user = new User("John");
user.sayHi();

Quando viene chiamato new User("John"):

  1. Viene creato un nuovo oggetto;
  2. Il metodo constructor() viene richiamato e assegna a this.name l’argomento dato.

…Ora possiamo chiamare i metodi, per esempio user.sayHi.

Niente virgole tra i metodi

Un errore comune per i principianti è separare i metodi con delle virgole, portando ad un syntax error.

La notazione delle classi non va confusa con la notazione letterale per gli oggetti. In una classe non sono richieste virgole.

Cos’è una classe?

Dunque, cos’è esattamente una class? A differenza di ciò che si potrebbe pensare, non si tratta di un concetto completamente nuovo.

Vediamo quindi cos’è effettivamente una classe. Questo ci aiuterà a comprendere aspetti più complessi.

In JavaScript, una classe è una specie di funzione.

Osserva:

class User {
  constructor(name) { this.name = name; }
  sayHi() { alert(this.name); }
}

// prova: User è una funzione
alert(typeof User); // function

Il costrutto class User {...} dunque:

  1. Crea una funzione chiamata User, che diventa il risultato della dichiarazione della classe. Le istruzioni della funzione provengono dal metodo constructor (considerato vuoto se non presente);
  2. Salva tutti i metodi (come sayHi) all’interno di User.prototype.

Quando richiameremo da un oggetto un metodo, questo verrà preso dal prototipo (prototype), come descritto nel capitolo F.prototype. Dunque un oggetto new User ha accesso ai metodi della classe.

Possiamo rappresentare il risultato della dichiarazione di class User come:

Il codice seguente ti permetterà di analizzarlo:

class User {
  constructor(name) { this.name = name; }
  sayHi() { alert(this.name); }
}

// una classe è una funzione
alert(typeof User); // function

// ...o, più precisamente, il costruttore
alert(User === User.prototype.constructor); // true

// I metodi sono in User.prototype:
alert(User.prototype.sayHi); // il codice del metodo sayHi

// ci sono due funzioni all'interno del prototipo
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi

Non solo una semplificazione (syntax sugar)

Talvolta si pensa che class in JavaScript sia solo “syntax sugar” (una sintassi creata per semplificare la lettura, ma che non apporta nulla di nuovo), dato che potremmo potremmo dichiarare la stessa cosa senza utilizzare la parola chiave class:

// la classe User usando solo funzioni

// 1. Costruttore
function User(name) {
  this.name = name;
}
// tutte le funzioni hanno un costruttore predefinito (di default)
// dunque non va creato

// 2. Aggiungiamo un metodo al prototipo
User.prototype.sayHi = function() {
  alert(this.name);
};

// Utilizzo:
let user = new User("John");
user.sayHi();

Il risultato di questo codice è circa lo stesso. È quindi logico pensare che class sia solo una semplificazione sintattica (syntax sugar).

Ci sono però delle importanti differenze.

  1. Una funzione creata attraverso class viene etichettata dalla speciale proprietà interna [[IsClassConstructor]]: true. Quindi non è esattamente uguale che crearla manualmente.

    A differenza di una normale funzione, il costruttore di una classe può essere richiamato solo attraverso la parola chiave new:

    class User {
      constructor() {}
    }
    
    alert(typeof User); // funzione
    User(); // Errore: Il costruttore della classe può essere richiamato solo attraverso 'new'

    Inoltre, nella maggior parte dei motori JavaScript il costruttore comincia con “class”

    class User {
      constructor() {}
    }
    
    alert(User); // class User { ... }

    Ci sono altre differenze, che scopriremo più avanti.

  2. I metodi delle classi non sono numerabili. La definizione di una classe imposta il flag enumerable a false per tutti i metodi all’interno di "prototype".

    Questo è un bene, dato che non vogliamo visualizzare i metodi quando utilizziamo un ciclo for..in per visualizzare un oggetto.

  3. Il contenuto di una classe viene sempre eseguito in strict.

    Oltre a queste, la sintassi class apporta altre caratteristiche, che esploreremo più avanti.

L’espressione class

Come le funzioni, le classi possono essere definite all’interno di un’altra espressione, passata come parametro, essere ritornata (returned), assegnata (assigned) ecc.

Qui c’è un piccolo esempio:

let User = class {
  sayHi() {
    alert("Hello");
  }
};

In maniera simile alle funzione nominate (Named Function Expression), le classi possono avere o meno un nome.

Se una classe ha un nome, esso è visibile solo all’interno della classe:

// "Named Class Expression"
// (la classe non ha un nome)
let User = class MyClass {
  sayHi() {
    alert(MyClass); // MyClass è visibile solo all'interno della classe
  }
};

new User().sayHi(); // funziona, restituisce la definizione di MyClass

alert(MyClass); // errore, MyClass non è visibile al di fuori della classe

Possiamo anche creare delle classi “on-demand”: