Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Ottimizzare JavaScript per mobile e browser apps

Link copiato negli appunti

Le applicazioni Web basate su JavaScript sono vere e proprie applicazioni moderne, interattive e multimediali (come i giochi e le applicazioni HTML5 create per il desktop), con richieste hardware talvolta anche elevate.

Questo fenomeno ha scatenato una nuova engine war, con tutti i maggiori produttori di browser impegnati a scontrarsi aspramente nel tentativo di creare e dotare il proprio software dell'ambiente che fornisce - da tutti i punti di vista - la migliore esperienza utente in assoluto nell'esecuzione del codice JavaScript.

Test e calcolo delle performance

L'esperienza utente che delle applicazioni JavaScript non è determinata esclusivamente dalle caratteristiche del motore che le esegue: è molto importante il codice JavaScript che scriviamo e le pratiche messe in campo (e da evitare) per renderlo maggiormente "digeribile" dal browser.

In altre parole, una buona applicazione è risultato anche di una stesura del codice attenta all'ottimizzazione. I compilatori JIT faranno solo la loro parte, trasformando il codice in sequenze eseguibili dalla macchina.

Per eseguire test di massima delle performance di un engine JavaScript, i produttori si affidano principalmente a una serie di benchmark: determinare la bontà generale del motore non è un compito facile, così i benchmark costituiscono almeno uno strumento capace di fornire dati misurabili dai quali partire per ottimizzare ulteriormente il prodotto.

Per eseguire in proprio un test delle capacità del motore del proprio browser è possibile, ad esempio, utilizzare il Bubblemark animation test di Alexey Gavrilov, disponibile anche per altre tecnologie generalmente impiegate nei browser (come Silverlight/WPF, Flash, Adobe AIR e Java).

È altrettanto importante testare il proprio codice, o meglio i componenti di cui è composto, creando ad esempio degli Unit Test con uno dei tanti framework disponibili, come Jasmine (derivato da JSUnit), oppure appoggiandosi a siti che consentono di eseguire i test direttamente online e condividerne i risultati, come nel caso di JSPerf.

Ridurre gli accessi superflui al DOM

Quando occorre manipolare le proprietà di un oggetto tramite JavaScript, occorre evitare la ripetizione inutile di accesso alle funzionalità del DOM, ad esempio la ricerca di un elemento in base al suo nome tramite getElementById():

document.body.box.getElementById('item1').style.left = coords.x + "px";
document.body.box.getElementById('item1').style.top = coords.y + "px";

Lo stesso vale per l'accesso agli oggetti che costituiscono le proprietà dell'elemento ottenuto dal DOM (come nel caso di style nell'esempio sopra). La situazione ovviamente peggiora se l'accesso al DOM avviene attraverso librerie e framework, come JQuery, tramite l'uso di espressioni e filtri.

Il codice dell'esempio potrebbe essere ottimizzato in questo modo:

var itemStyle = document.body.box.getElementById('item1').style;
itemStyle.left = coords.x + "px";
itemStyle.top = coords.y + "px";

È molto importante ricordare che le applicazioni girano anche su dispositivi mobili, per questo diventa importante ottimizzare cicli di calcolo e di conseguenza l'uso della batteria, come stiamo per vedere.

requestAnimationFrame: risparimare batteria nelle animazioni

È importante evitare di fare lavoro inutile, cioè di impegnare la CPU più del necessario quando non occorre, riducendo la fluidità dell'applicazione.

Un caso tipico è quello che si verifica quando si vogliono animare elementi nella pagina; molti si appoggiano all'uso delle funzioni setInterval() o setTimeout() per invocare periodicamente la funzione di aggiornamento grafico, specificando il minor tempo possibile per l'intervallo tra una chiamata e l'altra, cioè 0 (zero):

setInterval(myDrawRoutine, 0);

Questo metodo consuma molta CPU (e pertanto più batteria, risorsa critica su dispositivi mobili e tablet) e lascia meno spazio all'elaborazione dei calcoli tradizionali rallentando le operazioni in background. Al suo posto occorre utilizzare la funzione requestAnimationFrame(), che consente di sincronizzare le operazioni di disegno con la frequenza corretta di refresh dello schermo.

requestAnimationFrame prende come parametro la funzione di callback da richiamare ad ogni frame e ritorna un codice requestID.

Polyfill per requestAnimationFrame

Per evitare problemi di compatibilità è possibile sfruttare un semplice polyfill che faccia come suggerito qui:

(function() {
  var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
                              window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
  window.requestAnimationFrame = requestAnimationFrame;
})();

Anche se troviamo qualcosa di più robusto su questo articolo di Paul Irish

Già che ci siamo riportiamo anche un esempio:

function step(time) {
	console.log(time);  
	if (time < 150) {
		requestAnimationFrame(step);
	}
}
requestAnimationFrame(step);

Come si capisce facilmente mandando in esecuzione lo snippet, il parametro time della callback viene utilizzato come contatore del tempo passato dalla prima richiesta. Ecco il tracciato della console:

ALT_TEXT

In questa pagina è possibile vedere un esempio significativo di questo problema e l'efficacia della soluzione proposta.

ALT_TEXT

Evitare le conversioni automatiche tra tipi

Occorre prestare particolare attenzione al tipo di dato a cui appartengono le proprietà e gli argomenti delle funzioni JavaScript, evitando la conversione automatica operata da JavaScript quando tali valori vengono assegnati a variabili o parametri di tipo diverso, soprattutto se tale operazione viene ripetuta più volte, periodicamente o all'interno di un ciclo.

In questi casi, è bene costruirsi una cache del valore convertendolo tramite le funzioni incorporate in JavaScript (come parseInt() e toString()) e memorizzandolo in una variabile di appoggio e usando poi tale variabile al posto dell'originale.

Bilanciare la flessibilità del linguaggio con le performance

Il linguaggio JavaScript supporta molti "automatismi" che conferiscono flessibilità nella scrittura del codice: è possibile aggiungere dinamicamente proprietà a un oggetto, moltiplicare tra loro numeri e stringhe, convertire automaticamente tipi di dati; tuttavia è necessario tenere ben presente che queste operazioni, sebbene semplifichino il codice scritto in termini di forma e quantità, possono avere un impatto sulle performance dell'applicazione.

La risposta a questo problema non è ovviamente quella di abbandonare completamente queste feature, magari finendo con il complicare a tal punto il codice del programma da renderlo illeggibile e andando alla ricerca spasmodica della forma più ottimizzata possibile sacrificandone la comprensibilità; documentazione alla mano, occorre approfondire come opera la funzione che si sta utilizzando in una determinata riga di codice e saper scegliere il modo più adatto di ottenere lo stesso risultato senza eccedere in sintesi e cripticità del codice, ma nemmeno abusando degli automatismi del linguaggio.

Velocizzare l'accesso alle proprietà degli oggetti

Vi sono alcune best practice da seguire per creare "oggetti veloci". Una di queste riguarda l'accesso alle proprietà, evitando l'uso dell'operatore delete per rimuovere interamente una proprietà da un oggetto, operazione particolarmente lenta in JavaScript. È preferibile invece adottare un valore di default e assegnarlo alla proprietà, scegliendo comunque di mantenerla piuttosto che rimuoverla.

Analogamente, è preferibile definire tutte le proprietà all'interno del costruttore, astenendosi dall'aggiungerne dinamicamente in un secondo momento, poiché l'engine ottimizza l'accesso alle prime dando per scontato che siano quelle più frequentemente utilizzate nel resto dell'applicazione.

Infine, è bene non abusare dei "getter" e dei "setter" poiché introdotti nelle versioni recenti dello standard del linguaggio e non ottimizzati da tutti i compilatori JavaScript.

Velocizzare le operazioni matematiche

Le operazioni matematiche in JavaScript possono risultare poco performanti quando si utilizzano variabili di tipo differente nel contesto di una espressione, poiché JavaScript tende a generare oggetti, allocati nella memoria heap, per conservare al loro interno i valori coinvolti nel calcolo dell'espressione. Il fenomeno è noto con il nome di boxing.

È meglio cercare innanzitutto di uniformare i tipi e sforzarsi quando possibile di eseguire operazioni tra valori numerici, meglio se interi, potendo così sfruttare l'ottimizzazione applicata dal compilatore JavaScript nel calcolo allocando tali valori nello stack. Ciò vale anche per i numeri decimali a virgola mobile (float), da utilizzare tuttavia solo quando servono dato che sono composti da 64 bit e pertanto sono più lenti rispetto agli interi.

È importante infine utilizzare in modo consistente variabili e parametri, facendo assegnazioni che non ne alterino il tipo originale di appartenenza.

Ottimizzare l'uso degli array

Anche nell'uso degli array vi sono diverse ottimizzazioni applicabili: ad esempio, è meglio preallocare lo spazio necessario agli array per il numero di elementi che dovrà contenere, evitando di modificarne "al volo" le dimensioni in seguito, a meno che non sia necessario.

Per memorizzare valori numerici all'interno di vettori, è possibile ricorrere all'uso dei typed arrays: si tratta di implementazioni (purtroppo non disponibili in tutti i compilatori JavaScript) che consentono di dichiarare array del tipo numerico che si intende utilizzare; ad esempio, un Float64Array definisce un vettore di valori float a 64 bit. Questo tipo di array gode di diverse ottimizzazioni, quali l'allocazione sullo stack piuttosto che nella memoria heap.

Ti consigliamo anche