Possiamo anche assegnare metodi alle classi stesse, non solamente al loro "prototype"
. Questi metodi sono detti statici.
All’interno della classe, questi vengono preceduti dalla keyword static
, come possiamo vedere nell’esempio:
class User {
static staticMethod() {
alert(this === User);
}
}
User.staticMethod(); // true
Questo avrà lo stesso effetto di assegnarla direttamente come proprietà:
class User { }
User.staticMethod = function() {
alert(this === User);
};
User.staticMethod(); // true
Il valore di this
nella chiamata User.staticMethod()
è rappresentato dal costruttore dell classe User
(la regola dell’ “oggetto prima del punto”).
Solitamente, i metodi statici vengono utilizzati per rappresentare funzioni che appartengono alla classe, ma non ad un oggetto in particolare.
Ad esempio, potremmo avere degli oggetti di tipo Article
e necessitare di una funzione per confrontarli. Una soluzione naturale sarebbe quella di aggiungere il metodo Article.compare
, come nell’esempio:
class Article {
constructor(title, date) {
this.title = title;
this.date = date;
}
static compare(articleA, articleB) {
return articleA.date - articleB.date;
}
}
// usage
let articles = [
new Article("HTML", new Date(2019, 1, 1)),
new Article("CSS", new Date(2019, 0, 1)),
new Article("JavaScript", new Date(2019, 11, 1))
];
articles.sort(Article.compare);
alert( articles[0].title ); // CSS
Qui Article.compare
sta “al di sopra” degli articoli, poiché ha lo scopo di confrontarli. Non è un metodo di un articolo, ma piuttosto dell’intera classe.
Un altro esempio comune è quello del “factory method” (un particolare design pattern). Immaginiamo di avere bisogno di diverse modalità di creazione di un articolo:
- Creazione con i parametri forniti (
title
,date
etc). - Creazione di un articolo vuoto con la data di oggi.
- …o qualsiasi altra modalità.
Il primo metodo può essere implementato tramite il costruttore. Mentre per il secondo, possiamo creare un metodo statico appartenente alla classe.
Come Article.createTodays()
nell’esempio:
class Article {
constructor(title, date) {
this.title = title;
this.date = date;
}
static createTodays() {
// ricorda, this = Article
return new this("Today's digest", new Date());
}
}
let article = Article.createTodays();
alert( article.title ); // Today's digest
Ora, ogni volta in cui avremo bisogno di crare un “today’s digest”, possiamo invocare Article.createTodays()
. Ripetiamolo nuovamente, questo non è un metodo per uno specifico articolo, ma piuttosto un metodo dell’intera classe.
I metodi statici vengono utilizzati anche nelle classi database-related (relative a database), per poter cercare/salvare/rimuovere elementi dal database, come nell’esempio:
// assumiamo che Article sia una classe speciale per la gestione degli articoli
// metodo statico per la rimozione di un articolo:
Article.remove({id: 12345});
Proprietà statiche
E’ anche possibile definire proprietà statiche, queste sono molto simili alle proprietà della classe, ma sono precedute dalla keyword static
:
class Article {
static publisher = "Ilya Kantor";
}
alert( Article.publisher ); // Ilya Kantor
Lo stesso che si otterrebbe con un assegnazione diretta ad Article
:
Article.publisher = "Ilya Kantor";
Ereditarietà delle proprietà e dei metodi statici
Anche le proprietà ed i metodi statici vengono ereditati.
Ad esempio, Animal.compare
e Animal.planet
nel codice sotto, vengono ereditate e diventano quindi accessibili come Rabbit.compare
e Rabbit.planet
:
class Animal {
static planet = "Earth";
constructor(name, speed) {
this.speed = speed;
this.name = name;
}
run(speed = 0) {
this.speed += speed;
alert(`${this.name} runs with speed ${this.speed}.`);
}
static compare(animalA, animalB) {
return animalA.speed - animalB.speed;
}
}
// Eredita da Animal
class Rabbit extends Animal {
hide() {
alert(`${this.name} hides!`);
}
}
let rabbits = [
new Rabbit("White Rabbit", 10),
new Rabbit("Black Rabbit", 5)
];
rabbits.sort(Rabbit.compare);
rabbits[0].run(); // Black Rabbit runs with speed 5.
alert(Rabbit.planet); // Earth
Ora, quando invochiamo Rabbit.compare
, verrà invocato il metodo Animal.compare
ereditato.
Come funziona? Nuovamente, utilizzando il prototypes. Come potrete aver già intuito, extends
fornisce a Rabbit
il riferimento a [[Prototype]]
di Animal
.
- La funzione
Rabbit
eredita dalla funzione diAnimal
. Rabbit.prototype
eredita il prototye diAnimal.prototype
.
Come risultato, l’ereditarietà funziona sia per i metodi regolari che per quelli statici.
Ora, verifichiamo quanto detto guardando al codice:
class Animal {}
class Rabbit extends Animal {}
// per proprietà statiche
alert(Rabbit.__proto__ === Animal); // true
// per proprietà regolari
alert(Rabbit.prototype.__proto__ === Animal.prototype); // true
Riepilogo
I metodi statici vengono utilizzati per funzionalità che appartengono all’intera classe. Non hanno nulla a che fare con l’istanza della classe.
Ad esempio, un metodo per il confronto Article.compare(article1, article2)
o un factory method Article.createTodays()
.
Queste vengono precedute dalla keyword static
all’interno della dichiarazione della classe.
Le proprietà statiche vengono utilizzate quando si ha intenzione di memorizzare dati relativi alla classe, che non sono quindi legati ad un’istanza precisa.
La sintassi è:
class MyClass {
static property = ...;
static method() {
...
}
}
Tecnicamente, le dichiarazioni di proprietà statiche equivalgono all’assegnazione diretta alla classe stessa:
MyClass.property = ...
MyClass.method = ...
Le proprietà ed i metodi statici vengono ereditati.
Nel caso in cui class B extends A
il prototype della classe B
punta ad A
: B.[[Prototype]] = A
. Quindi se un campo non viene trovato in B
, la ricerca continuerà in A
.