Capita che si voglia rispondere a un evento ricorrente una sola volta e ignorarlo le successive, per esempio fare qualcosa solo per la prossima pagina caricata o per il prossimo pacchetto proveniente da una connessione di rete.
La prima strada presa in genere è quella del flag esterno...
var myApp = {
init: function() {
this._ignorePageLoads = true;
getBrowser().addEventListener('load', function(event) {
if(myApp._ignorePageLoads)
return;
alert('Pagina caricata!');
}, false);
},
watchNextPageLoad: function() {
this._ignorePageLoads = false;
}
};
myApp.init();
Quando vorremo tener d'occhio il prossimo caricamento di pagina, scriveremo:
myApp.watchNextPageLoad();
L'approccio funziona, ma a che prezzo?
- L'aggiunta di una "variabile privata" (in termini di OOP classica), _ignorePageLoads fa straripare in un ambito più ampio quel che dovrebbe essere limitato alle immediate vicinanze del listener.
- Un event listener sempre attivo. Certo, il 99% delle volte eseguirà solo un controllo per vedere se fare altro, ma intanto è stato registrato e viene regolarmente invocato.
La soluzione è rimuovere il listener quando ha svolto il suo compito, usando DOMElement.removeEventListener, che deve essere chiamato con esattamente gli stessi parametri che sono stati passati all'addEventListener corrispondente.
var myApp = {
watchNextPageLoad: function() {
getBrowser().addEventListener('load', this._onLoad, false);
},
_onLoad: function(event) {
getBrowser().removeEventListener('load', this._onLoad, false);
alert('Pagina caricata!');
}
};
Stavolta non c'è neanche bisogno di un myApp.init(): basterà invocare myApp.watchNextPageLoad al momento opportuno.
Per avere qualcosa da passare a removeEventListener, abbiamo dovuto spostare il listener in una funzione a parte, in modo da potercisi riferire da due luoghi diversi. Possiamo fare di meglio, e nascondere _onLoad al mondo esterno, sfruttando il fatto che in JavaScript possiamo definire funzioni in qualunque ambito.
var myApp = {
watchNextPageLoad: function() {
function onLoad() {
getBrowser().removeEventListener('load', onLoad, false);
alert('Pagina caricata!');
}
getBrowser().addEventListener('load', onLoad, false);
}
};
In questo modo onLoad è accessibile solo dall'interno di watchNextPageLoad.
Infine, l'ultimo colpetto. Potrà piacere o meno; c'è chi sostiene che aggiungere nomi descrittivi aiuta la lettura; e chi, specie per codice già evidente di per sé, ritiene troppi nomi un ennesimo strato di indirezione da perforare per capire cosa fa un programma. Nel secondo caso, questa sarà una modifica benvenuta:
var myApp = {
watchNextPageLoad: function() {
getBrowser().addEventListener('load', function(event) {
getBrowser().removeEventListener('load', arguments.callee, false);
alert('Pagina caricata!');
}, false);
}
};
Il trucco è basato sull'uso della proprietà "callee" della variabile speciale "arguments". La variabile "arguments" normalmente rappresenta gli argomenti passati a una funzione, per esempio invocando "foo(42, 'galaxy')", "arguments[0]" sarà 42 e "arguments[1]" sarà "galaxy". Ma arguments fornisce anche una reference alla funzione stessa in cui viene usato... reference che nell'ultima iterazione passiamo a removeEventListener.
Funzioni first-class, scoping lessicale e una reflection a volte un po' imboscata, rendono JavaScript estremamente malleabile. Questo è solo uno dei tanti modi in cui possiamo mescolare gli ingredienti. Buon divertimento!