Riduzione ai minimi termini di JavaScript, Parte II

Nel mio ultimo articolo, ho introdotto i concetti base per una migliore minimizzazione di JavaScript con lo YUI Compressor. Come molti lettori hanno astutamente sottolineato, si riduce tutto all’“evitare di usare eval() e l’istruzione with.” La premessa dell’articolo consisteva nell’assicurarsi che capiste come non tirarvi la zappa sui piedi usando lo YUI Compressor. Precedentemente, abbiamo imparato cosa non fare. Questo articolo enfatizza invece cosa dovreste fare per sfruttare al massimo la migliore feature di riduzione dello YUI Compressor: la sostituzione dei nomi delle variabili locali.

L’articolo prosegue sotto

Sostituzione del nome della variabile#section1

Sostituire i nomi delle variabili locali con delle alternative più brevi (di uno o due caratteri) costituisce il maggior risparmio di byte che lo YUI Compressor offra. Spesso, assegnare dei nomi alle variabili è fonte di angoscia nella stesura del codice, ma fondamentalmente, i nomi delle variabili hanno un senso solo per le persone che devono cercare di capire il codice. Una volta che siete sicuri che non ci sono esseri umani che devono interpretare il vostro codice, le variabili diventano semplicemente dei segnaposto generici per i valori.

Quando usate JavaScript in produzione, il vostro unico utente è il browser e al browser non interessa affatto che nomi usiate per le variabili. Per questa ragione, lo YUI Compressor sostituisce questi nomi di variabile con delle versioni più corte. Dal momento che la maggior parte del debugging avviene su macchine dedicate allo sviluppo con il codice non ancora ridotto in dimensione, questa pratica non influisce sul lavoro di ingegneria. Tuttavia, se vi capita di rimanere bloccati avendo necessità di fare debugging su qualcosa che è già in produzione, sappiate che esistono dei modi per rendere più semplice il debug di codice già ridotto.

Che cosa non può essere sostituito?#section2

Prima di parlare delle tecniche per la sostituzione ottimale delle variabili, è importante comprendere quali parti di codice non possono essere rimpiazzate. Spesso si pensa che quanto segue sia sostituibile:

  • nomi di proprietà (object.property),
  • parole chiave (if, var, return, etc.) e
  • variabili globali

Molti sviluppatori non si soffermano a pensare a quante volte si accede ad una proprietà o a quante parole chiave di JavaScript usino quando scrivono il loro codice. Considerando i nomi di proprietà, le parole chiave e le variabili globali all’interno della struttura del vostro codice, si potrebbe giungere ad un rapporto di riduzione molto maggiore.

Nomi di proprietà#section3

Mentre può fare la sostituzione di variabile sulle variabili locali, non c’è nulla che lo YUI Compressor possa fare per le proprietà. Dal momento che i nomi delle proprietà sono definiti sull’oggetto stesso, non c’è un modo per rilevarle o per sostituirle in maniera sicura. Ciò significa che il codice che accede a molte proprietà sarà più grande del codice che non vi accede. Ad esempio:

(Gli a-capo sono contrassegnati da » —Ed.)


function toggleImage(id){
if (document.getElementById(id).src.indexOf("1.png") > -1){
document.getElementById(id).src = document.getElementById »
(id).src.replace("1.png", "2.png");
}
}

Questa funzione sostituisce l’immagine visualizzata con un’altra manipolando la proprietà src. Sfortunatamente, la funzione ridotta risultante è più o meno della stessa dimensione (168 byte vs. 205 byte):


function toggleImage(a){ if(document.getElementById(a).src.indexOf »
("1.png")>-1) {document.getElementById(a).src=document.getElementBy »
Id(a).src.replace("1.png","2.png")}};

Notate come document.getElementById(a).src sia ripetuto tre volte, il che è quasi esattamente la stessa cosa dell’originale (con l’eccezione del nome della variabile).

Quando la stessa proprietà dell’oggetto è usata frequentemente, la migliore strategia è quella di memorizzare il valore della proprietà in una variabile locale e poi usare questa variabile locale. Il codice precedente può essere riscritto così:


function toggleImage(id){
var image = document.getElementById(id);
if (image.src.indexOf("1.png") > -1){
image.src = image.src.replace("1.png", "2.png");
}
}

Non solo questa versione del codice è più piccola della precedente (185 byte), ma la versione ridotta è ancora più piccola:


function toggleImage(b){var a=document.getElementById(b);if(a.src. »
indexOf("1.png")>-1){a.src=a.src.replace("1.png","2.png")}};

Questo codice è di soli 126 byte—molto più piccolo del codice originale e ancora più piccolo del codice originale a cui è stata applicata la riduzione. Si ha questo risparmio senza alcun cambiamento nella funzionalità, semplicemente usando un po’ di rifattorizzazione. Vale la pena notare che questo cambiamento migliora anche la performance a runtime, poiché non si deve richiamare tre volte document.getElementById().

Tenete sempre presente che qualunque cosa appare dopo un punto in JavaScript (object.property) non può essere ulteriormente ridotto. Quando riducete il numero di punti nel vostro codice, riducete anche la dimensione complessiva una volta che il codice sarà sottoposto a minimizzazione.

Parole chiave#section4

Quando esaminate un qualunque file JavaScript di grosse dimensioni, potete notare quante parole chiave vengano usate. Parole chiave come var, if, for e return sono tutte regolarmente usate per ottenere la funzionalità desiderata. Il problema è che si può fare un uso eccessivo di queste parole chiave e, in tal modo, si inficia la riduzione della dimensione del file. Le due parole chiave più usate sono var e return.

L’istruzione var definisce una o più variabili. Vi capiterà a volte di vedere del codice come questo:


var image = document.getElementById("myImage");
var div = document.getElementById("myDiv");

Questo codice definisce due variabili, una dopo l’altra. Spesso vedo questo pattern per 10 o più variabili in fila. Fare così gonfia artificialmente la dimensione del vostro codice per più istruzioni var possono essere combinate in un’unica separate dall’operatore virgola:


var image = document.getElementById("myImage"),
div = document.getElementById("myDiv");

Questo codice definisce anch’esso le due variabili e ne fornisce la stessa inizializzazione. In questo modo, avete risparmiato i tre byte che avrebbe occupato un’altra var. Tre byte potrebbero non sembrare un granché, ma se siete in grado di trovare una dozzina di posti in cui è presente al momento un’istruzione var extra, allora comincerete a risparmiare sul serio. Il miglior consiglio è di usare una istruzione var all’inizio di ciascuna funzione per definire tutte le variabili che userete in quella funzione. Lo YUI Compressor rileverà le funzioni con più di un’istruzione varquando la flag –v è in uso (anche JSLint può fare questo).

La seconda istruzione, return, è tipicamente usata eccessivamente in questo modo:


function getValueFor(data){

if (firstCondition(data)){
return 1;
} else if (secondCondition(data)){
return 2;
} else if (thirdCondition(data)){
return 3;
} else {
return 4;
}
}

Fondamentalmente, questa funzione si conclude ritornando un valore basato sulla valutazione di alcune condizioni. La versione minimizzata è di 146 byte. Potete risparmiare dei byte usando una singola istruzione return:


function getValueFor(data){

var value;

if (firstCondition(data)){
value = 1;
} else if (secondCondition(data)){
value = 2;
} else if (thirdCondition(data)){
value = 3;
} else {
value = 4;
}

return value;
}

Il codice riscritto rimpiazza la maggior parte delle istanze di return con una variabile locale che può essere ridotta. La versione minimizzata di questo codice è 140 byte. Il codice in realtà può essere ulteriormente rifattorizzato:


function getValueFor(data){

var value = 4;

if (firstCondition(data)){
value = 1;
} else if (secondCondition(data)){
value = 2;
} else if (thirdCondition(data)){
value = 3;
}

return value;
}

Questo codice si riduce a 133 byte e produce lo stesso risultato. Notate che questa versione rimuove inoltre una parola chiave in più, else, rendendolo ancore più piccolo.

Senza avere la pretesa di riaprire l’annoso dibattito riguardo la bontà e/o necessità di un singolo exit point, usare una sola istruzione return per ciascuna funzione fa diminuire la dimensione delle vostre funzioni una volta minimizzate.

Variabili globali#section5

Come citato precedentemente, la sostituzione del nome della variabile può avvenire solo sulle variabili locali. Cercare di sostituire i nomi delle variabili globali, inclusi i nomi di funzioni globali, può risultare in codice non funzionante perché lo YUI Compressor non ha modo di sapere in quale altro luogo queste variabili e funzioni potrebbero essere usate. Questo si applica sia alle variabili globali predefinite come window e document, sia alle variabili globali che create voi stessi.

Salvate le variabili globali localmente#section6

Se necessitate di una variabile globale più di una volta in una funzione, è meglio salvare quella variabile globale in una variabile locale. Questo ha due importanti vantaggi: è più rapido accedere a variabili locali a runtime e, una volta immagazzinate in una variabile locale, lo YUI Compressor può sostituire il nome della variabile. Ad esempio:


function createMessageElement(message){
var div = document.createElement("div");
div.innerHTML = message;
document.body.appendChild(div);
}

Questa funzione ha due variabili locali, message e div, così come una variabile globale, document. Osservate cosa succede quando il codice viene ridotto:


function createMessageElement(a){var b=document.createElement("div"); »
b.innerHTML=a;document.body.appendChild(b)};

Potete chiaramente notare che document appare due volte nel codice minimizzato. Avete risparmiato 43 byte (158 l’originale, 115 quello ridotto), il che non è male, ma si può fare di meglio. Salvando document in una variabile locale, potete risparmiare ancora di più. Ecco il codice riscritto:


function createMessageElement(message){
var doc = document,
div = doc.createElement("div");
div.innerHTML = message;
doc.body.appendChild(div);
}

La versione ridotta di questo codice risulta così:


function createMessageElement(a){var b=document,c=b.createElement »
("div");c.innerHTML=a;b.body.appendChild(c)};

Ancora una volta, sebbene il codice originale sia leggermente più grande (175 byte), la versione ridotta è decisamente più piccola (110 byte). Il risparmio in questo caso è di ulteriori cinque byte che potrebbero non sembrare molti, ma questa funzione utilizza document soltanto due volte; se venisse usata con maggior frequenza, il risparmio sarebbe molto più grande.

Evitate di creare variabili globali#section7

Lo YUI Compressor non può sostituire le variabili globali, incluse quelle che definite voi stessi. Per questa ragione, è meglio ridurre al minimo il numero di variabili e funzioni globali che introducete (questa è inoltre considerata una buona pratica per la mantenibilità del codice). Considerate il seguente esempio:


var helloMessage = "Hello world!";
function displayMessage(){
alert(helloMessage);
}
displayMessage();

In questo codice, sia helloMessage sia displayMessage() sono globali e quindi non si può sostituirne i nomi. Il codice minimizzato risultante è come segue:


var helloMessage="Hello world!";function displayMessage(){alert »
(helloMessage)}displayMessage();

Laddove il codice originale era di 113 byte, il codice compresso è ora di 95 byte: non un risparmio enorme.

Nella maggior parte dei casi, non avete effettivamente bisogno delle variabili globali per portare a termine i task. Questo codice, ad esempio, funziona altrettanto bene quando entrambe helloMessage e displayMessage() sono variabili locali. Potete creare tutte le variabili e funzioni in un dato blocco di codice creando attorno ad esso una funzione anonima self-executing.

Una funzione self-executing ha un aspetto un po’ strano:


(function(){
//code here
})();

Questo codice crea una funzione e va in esecuzione immediatamente. E’ simile a questa:


function doSomething(){
//code here
}
doSomething();

Dal momento che la funzione verrà chiamata solo una volta, potete risparmiare dei byte eliminandone il nome. Una funzione self-executing è il modo più veloce per creare una funzione ed eseguirla esattamente una volta. E’ inoltre molto semplice includere del codice già esistente all’interno della funzione self-executing. Ecco come fare:


(function(){

var helloMessage = "Hello world!";
function displayMessage(){
alert(helloMessage);
}
displayMessage();

})();

All’interno della funzione self-executing, helloMessage e displayMessage() sono locali. La versione ridotta di questo codice è come segue:


(function(){var b="Hello world!";function a(){alert(b)}a()})();

Notate che, sebbene il codice originale sia più grande, pesando 154 byte, la versione ridotta è di soli 63 byte (32 byte più piccola del codice originale minimizzato).

Unire le tecniche#section8

Potete migliorare ulteriormente l’efficacia di una funzione self-executing passando le variabili globali come argomenti. Una volta all’interno della funzione, gli argomenti sono variabili locali, così avete automaticamente creato l’opportunità per una migliore minimizzazione. Supponete di avere il seguente codice:


var helloMessage = "Hello world!";

function createMessageElement(message){
var div = document.createElement("div");
div.innerHTML = message;
document.body.appendChild(div);
}

createMessageElement(helloMessage);

Questo codice crea una funzione globale, createMessageElement(), e questa funzione usa l’oggetto globale document. C’è anche la variabile globale helloMessage. Quando viene ridotta da sola, ottenete un totale di 179 byte. Includendola in una funzione self-executing, fate diventare createMessageElement() e helloMessage locali:


(function(){
var helloMessage = "Hello world!";

function createMessageElement(message){
var div = document.createElement("div");
div.innerHTML = message;
document.body.appendChild(div);
}

createMessageElement(helloMessage);
})()

L’unica variabile globale rimanente in questo codice è document e quindi la minimizzazione non è ottimale come potrebbe essere:


(function(){var a="Hello world!";function b(c){var d=document. »
createElement("div");d.innerHTML=c;document.body.appendChild(d)}b(a)})();

Il rapporto di compressione è piuttosto buono per questo esempio (283 byte a 135 byte, o 52%), ma potete fare ancora un po’ meglio rendendo document una variabile locale. Potete fare ciò facilmente passando document alla funzione self-executing come argomento:


(function(doc){
var helloMessage = "Hello world!";

function createMessageElement(message){
var div = doc.createElement("div");
div.innerHTML = message;
doc.body.appendChild(div);
}

createMessageElement(helloMessage);
})(document)

In questo codice, la variabile doc è locale alla funzione self-executing e così può essere sostituito dallo YUI Compressor. Il risultato è:


(function(c){var a="Hello world!";function b(d){var e=c.createElement »
("div");e.innerHTML=d;c.body.appendChild(e)}b(a)})(document);

Questo codice è di 130 byte, più piccolo sia dell’originale che dell’esempio precedente. Ancora una volta, avete maggior risparmio con questa tecnica quando le variabili globali vengono usate più di due volte.

Il caveat gzip#section9

Il passo successivo alla minimizzazione di JavaScript è la sua compressione e gzip è la forma più popolare di compressione. Il meccanismo di funzionamento di Gzip consiste nell’ottimizzazione delle ridondanze nel testo e le tecniche descritte in questo articolo rimuovono efficacemente alcune delle ridondanze che potrebbero normalmente essere compresse usando gzip.

Quando si esegue la rifattorizzazione del codice usando queste tecniche, dovremmo aspettarci di vedere il rapporto di compressione di gzip diminuire—il che vuol dire che la differenza in percentuale tra la dimensione del file ridotto e del file ridotto e zippato sarà minore di prima. In ultima istanza, quello a cui dovreste puntare è la versione zippata più piccola possibile e poi alla versione minimizzata più piccola possibile. La versione zippata è importante per il network transfer time mentre la versione minimizzata è importante per il parse time e per il caching.

Durante la stesura di questo articolo, mi sono imbattuto nella libreria open source XAuth di Meebo. Il JavaScript sembrava che potesse diventare più piccolo solo applicando alcune di queste tecniche. Ho passato un po’ di tempo rifattorizzando il codice e ho ottenuto dei risultati decenti (trovate il mio codice su GitHub).

Puro Minimizzato Minimizzato + Zippato % Gzip Diff
Originale 5720 b 1307 b 668 b 49% più piccolo
Refattorizzato 6143 b 1210 b 663 b 45% più piccolo

Sebbene la dimensione del puro codice sorgente sia aumentata di poche centinaia di byte, la versione ridotta è diminuita di quasi 100 byte, mentre la versione minimizzata e zippata si è ridotta di cinque byte. Notate che gzip è stato circa il 4% meno efficiente del codice rifattorizzato sebbene nel complesso la dimensione del codice fosse minore.

Conclusioni#section10

Le tecnica più potente per risparmiare byte con YUI Compresso è rimpiazzare le variabili con dei nomi di variabile di una o due lettere. Assicurarsi che il proprio codice sia impostato in maniera tale che questa sostituzione possa avvenire con la maggior frequenza possibile è importante per ottenere il codice più ridotto. Quando programmate, tenete a mente che i nomi delle proprietà, le parole chiave e le variabili globali rimangono così come sono nel codice minimizzato. Le tecniche presentate in questo articolo sono pensate per ridurre l’impatto di tali questioni nel codice minimizzato finale. Dovreste usare l’accortezza di testare il vostro codice rifattorizzato con gzip per assicurarvi di avere il codice più ridotto possibile, poiché rimuovere le ridondanze riduce l’efficienza di gzip.

Illustrazioni dell’articolo: {carlok}

Nessun commento

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Altro da ALA

Webwaste

In questo estratto da World Wide Waste, Gerry McGovern esamina l'impatto ambientale di siti web pieni zeppi di asset inutili. Digital is physical. Sembra economico e gratis ma non lo è: ci costa la Terra.
Industry