INTRODUZIONE
Il Javascript, a differenza di altri linguaggi, non consente di ridefinire il comportamento degli operatori quando interagiscono con gli oggetti che definiamo nel nostro programma, ma ci permette di modificare il valore nel quale un oggetto viene trasformato. Putroppo possiamo considerare questa opportunità solo un misero premio di consolazione, perché il controllo che possiamo ottenere è abbastanza limitato. Ad ogni modo potrebbe sempre tornarci utile conoscere quali possibilità il linguaggio ci offre, considerando che ES6 ha portato una soluzione dal valore non indifferente.
L’intero articolo si basa su un buon numero di concetti legati alla coercizione, perciò se non siete pratici di questo argomento vi consiglio di dare un’occhiata alla serie presente sul blog. Quello che possiamo fare, infatti, è modificare il risultato della coercizione eseguita sui nostri oggetti.
personalizzazione PRIMA DI es6
Dato che l’intero ecosistema della coercizione si basa sui metodi toString e valueOf richiamabili di default su qualsiasi oggetto (e sarà compito delle varie Abstract Operations farlo), quello che potevamo fare prima dell’avvento di ES6 era semplicemente ridefinire questi due metodi nei nostri oggetti.
Perché ho utilizzato la parola ridefinire? Perché i due metodi sono richiamabili di default? Dovete sapere che in Javascript tutti gli oggetti sono in automatico collegati all’oggetto Object.prototype tramite quella che viene definita prototype chain. Questo oggetto definisce un discreto numero di metodi che sono perciò invocabili direttamente dagli altri oggetti ad esso connessi. Se volete saperne di più sull’argomento consiglio la lettura di var object = {
prop: "value",
};
object.valueOf(); // object
object.toString(); // "[object Object]"
Ecco che il metodo valueOf si limita a restituire un riferimento all’oggetto stesso sul quale è stato invocato, mentre il metodo toString, leggendo il valore di una proprietà interna di nome [[Class]], costruisce la fin troppo comune stringa “[object Object]”. Anche gli array sono oggetti, ma per essi il comportamento del metodo toString è stato ridefinito: Quando un qualsiasi oggetto viene coerciso, a seconda delle condizioni iniziali, un metodo tra toString e valueOf avrà la precedenza. Solo nel caso in cui esso non restituisca un valore di tipo primitivo (come ad esempio il metodo valueOf di Object.prototype) l’altro metodo verrà invocato come ripiego. Se nemmeno esso restituisse un valore primitvo verrà sollevato un TypeError. Attenzione! Potremmo decidere di restituire null oppure undefined quando ridefiniamo questi metodi perché sono valori primitivi validi. Comunque il Javascript non porta mai a questo risultato per gli oggetti che ci mette a disposizione e dovremmo seguire il suo esempio, restituendo come valore primitivo uno tra stringa, numero e booleano. Operazioni numeriche come Vediamo, in pratica, come tutto questo può aiutarci a raggiungere il nostro scopo: Abbiamo perciò ridefinito il comportamento del nostro oggetto per le più comuni operazioni aritmetiche. Potremmo comunque non essere soddisfatti del risultato ottenuto nell’operazione Prendiamo il seguente esempio per afferrare bene questo concetto, ripercorrendo gli step seguiti dal linguaggio: Prima di addentrarci nell’ulteriore possibilità offerta da ES6 è bene prestare attenzione al concetto di hint. Questo perché ogni volta che la ToPrimitive Abstract Operation viene invocata, per decidere se dare priorità al metodo valueOf oppure al metodo toString, necessita di un “suggerimento”. Suggerimento che può prendere uno dei seguenti valori: number, string, default. Quando esso vale string verrà data precedenza al metodo toString, mentre quando vale number oppure default sarà il metodo valueOf ad avere priorità. L’unica eccezione è il tipo Date, per il quale il suggerimento default renderà prioritario il metodo toString. Vediamo quali suggerimenti vengono inoltrati dalle principali operazioni viste fin’ora:var array = [1, 2, 3];
array.valueOf(); // array
array.toString(); // "1,2,3"
Number(obj);
, +obj; // + unario
oppure - (unario e binario), *, **, /,
I dubbi sorgono quando si affronta il + binario e l’operatore == di loose equality i quali danno comunque priorità a valueOf, ma non posso fare altro che rimandarvi alla serie di articoli sulla coercizione per fugare ogni dubbio.
var myObj = {
toString: function() {
return "myObj";
},
valueOf: function() {
return 10;
}
}
Number(myObj); // 10
+myObj; // 10
myObj - 7; // 3
myObj
myObj + 15; // 25
"this is " + myObj; // "this is 10" -> l'operatore + binario invoca valueOf
String(myObj); // "myObj"
"this is " + myObj;
, ovvero “this is 10”, preferendo “this is myObj” ad esso. Purtroppo, esclusa la corcizione esplicita tramite la funzione String(), l’unico modo per ottenere ciò, in ES5, è imporre anche al metodo valueOf la restituzione della stringa “myObj” sacrificando la possibilità di utilizzare l’oggetto nelle operazioni aritmetiche, pena un’infinita serie di NaN derivanti dalla coercizione della stringa “myObj” in un numero. Questo perché quando la ToPrimitive Abstract Operation viene invocata e ci si aspetta che essa restituisca un valore di tipo numerico, se così non avviene il Javascript è costretto a trasformare il valore ottenuto in un numero.var obj = {};
obj - 10; // NaN
NaN - 7
che ha come risultato NaN.personalizzazione CON es6