Abbiamo visto le caratteristiche principali dei simboli ed abbiamo visto come è possibile identificarli utilizzando delle descrizioni. Ma che utilizzo concreto possiamo farne nelle nostre applicazioni JavaScript? Proviamo a suggerire i possibili utilizzi con qualche esempio.
Supponiamo di voler identificare un oggetto in maniera univoca assegnando un valore alla sua proprietà id:
var item = { descrizione: "Prodotto A", prezzo: 24.5 };
item.id = nuovoId();
L'approccio classico consiste nel prevedere una funzione, nuovoId()
nel nostro esempio, che gestisca un contatore globale da incrementare ad ogni nuova richiesta per un nuovo identificatore. Con i simboli possiamo evitare il ricorso a questa funzione scrivendo semplicemente:
item.id = Symbol();
È il runtime di JavaScript a generare per noi un valore univoco.
Un altro possibile utilizzo è nella definizione di enumerazioni. Dal momento che JavaScript non supporta nativamente le enumerazioni, un approccio molto diffuso prevede di definire delle variabili come nel seguente esempio:
var giorniDellaSettimana = {
lunedi: 1,
martedi: 2,
mercoledi: 3,
giovedi: 4,
venerdi: 5,
sabato: 6,
domenica: 7
};
Un problema con questo approccio è che eventuali funzioni che utilizzano i giorni della settimana definiti in questo modo stanno lavorando in realtà con valori interi e potrebbero essere utilizzati male, volontariamente o involontariamente. Immaginiamo la seguente funzione:
function isGiornoLavorativo(giorno) {
return (giorno != giorniDellaSettimana.sabato && giorno != giorniDellaSettimana.domenica);
}
console.log(isGiornoLavorativo(giorniDellaSettimana.lunedi)); //true
console.log(isGiornoLavorativo(giorniDellaSettimana.sabato)); //false
Essa restituisce true
o false
in base al fatto che il valore dell'enumerazione rappresenta un giorno lavorativo o meno. Nulla vieta però di passare alla funzione direttamente un numero o il risultato di una somma:
console.log(isGiornoLavorativo(3)); // true
console.log(isGiornoLavorativo(2+5)); //false
Questo crea non poca confusione. Possiamo evitare questo definendo l'enumerazione tramite simboli:
var giorniDellaSettimana = {
lunedi: Symbol(),
martedi: Symbol(),
mercoledi: Symbol(),
giovedi: Symbol(),
venerdi: Symbol(),
sabato: Symbol(),
domenica: Symbol()
};
Questo fa sì che a ciascun elemento dell'enumerazione venga assegnato un valore univoco ed è solo quel valore che rappresenta quello specifico giorno della settimana. Siamo "costretti
Un altro utilizzo è rappresentato dalla possibilità di definire proprietà per un oggetto senza il rischio di collisione di nomi. Supponiamo di avere necessità di aggiungere a runtime una proprietà ad un oggetto esistente, ad esempio per assegnare un valore che riutilizzeremo in un altro punto dell'applicazione:
var item = new Item();
item.dataControllo = new Date();
In questo esempio creiamo la proprietà dataControllo
item
dataControllo
item
item
dataControllo
In queste situazioni è meglio ricorrere ai simboli:
var item = new Item();
var dataControllo = Symbol();
item[dataControllo] = new Date();
In questo modo abbiamo la garanzia di non incorrere in conflitti di nomi nell'aggiunta di una nuova proprietà all'oggetto. Tra l'altro, la nuova proprietà non sarà visibile nell'elenco delle proprietà dell'oggetto accessibile tramite Object.getOwnPropertyNames()
Possiamo comunque accedere alle proprieta definite tramite simboli sfruttando il metodo Object.getOwnPropertySymbols():
var props = Object.getOwnPropertySymbols(item);
console.log(props.length); // risultato: 1
console.log(item[props[0]]); // risultato: valore della data assegnata
Un altro interessante utilizzo è quello del simbolo predefinito Symbol.iterator iterabile for...of
function Collection() {};
var collection = new Collection();
collection[0] = 123;
collection[1] = "test";
collection[2] = 222;
Per elencare sulla console il contenuto della collezione dovremmo ricorrere al metodo Object.getOwnPropertyNames()
var props = Object.getOwnPropertyNames(collection);
for (var i = 1; i < props.length; i++ ) {
console.log(collection[props[i]]);
}
Possiamo semplificare la vita a chi utilizzerà la nostra collezione rendendola iterabile. Per fare ciò, dobbiamo definire un metodo nel costruttore che abbia come nome Symbol.iterator
function Collection() {};
Collection.prototype[Symbol.iterator] = function() {
var self = this;
var i = 0;
return {
next: function() {
if (self[i] !== undefined) {
return { value: self[i++] };
} else {
return { done: true };
}
}
}
}
Come possiamo vedere, il nostro metodo restituisce un oggetto con un metodo next()
i
next()
value
done
true
Con l'implementazione di questo metodo, gli utilizzatori della nostra collezione potranno semplicemente utilizzare for...of per accedere al suo contenuto:
for (var x of collection) {
console.log(x);
}
In conclusione, questo nuovo tipo di dato è a prima vista un po' incomprensibile, ma una volta che abbiamo individuato le sue caratteristiche di base possiamo sfruttarlo per implementare funzionalità molto interessanti.