CogitoWeb

6 luglio 2010

Box Model: alla fine avevano ragione “loro”

Filed under: CSS — MaxArt @ 02:14
Tags: , , ,
Se vi dovessero chiedere la lunghezza della vostra auto, molto probabilmente voi prendereste un metro e misurereste la distanza tra il paraurti anteriore e quello posteriore. Non vi verrebbe mai in mente di dire che la lunghezza è la distanza tra il cruscotto ed il lunotto posteriore. Similmente, se vi chiedessero le dimensioni di una scatola di scarpe, voi la misurereste dai bordi esterni, e non quelli interni sebbene la differenza sia minima.

A questo devono aver pensato alla Microsoft tanto, tanto tempo fa, sviluppando la prima versione di Internet Explorer (era il 1995). La domanda era: come dobbiamo intendere le dimensioni di un elemento HTML? A cosa si definiscono i valori di width e di height? Come qualsiasi sviluppatore web saprà, un elemento HTML è caratterizzato da tre “spessori”:

  • margin: si tratta di uno spazio vuoto che viene frapposto tra l’elemento e quelli che lo circondano;
  • border: il bordo dell’elemento, disegnato in vari modi e colori;
  • padding: un ulteriore spazio che specifica la distanza tra il bordo dell’elemento ed il suo contenuto.

L’idea di Microsoft era che width e height si dovessero riferire alle dimensioni dell’elemento includendo bordi e padding, andando cioè così a delimitare l’area effettivamente visibile dell’elemento. Questa in effetti pare la scelta più logica, ma così non sembrò alla Netscape, sviluppatore del browser concorrente Navigator: per loro, la scelta migliore era definire le dimensioni come quelle del contenuto dell’elemento. Fu così che, per Internet Explorer, un elemento largo 100 pixel, con 5 pixel di bordo e 10 di padding, visualmente era largo effettivamente 100 pixel, mentre era largo 130 pixel per Netscape Navigator. E non c’era modo di conciliare la cosa: inutile dire che era un brutto periodo per i webmaster…

A questo si aggiunse che il consorzio W3 per la definizione degli standard web decise, a sorpresa, di dare ragione all’idea di Netscape: larghezza e altezza sono quelli del contenuto e non della scatola. Beh, poco male: in fondo l’importante era che venisse presa una decisione definitiva, perché in sostanza i due box model sono equivalenti, giusto? Sbagliato! Perché l’uso del box model di casa Microsoft, oltre ad essere intuitivamente più ragionevole, consente di “giocare” molto più liberamente con le dimensioni, permettendo in sostanza di mischiare le unità di misura. Cosa vuol dire questo?

Supponiamo di voler mettere un elemento <div> all’interno di un altro, in modo che occupi tutto lo spazio possibile in larghezza. Se conoscessimo la dimensione in pixel (o qualche altra unità di misura) la cosa sarebbe banale, ma in caso contrario si può sempre ricorrere alle percentuali, in questo modo:

<div>
   ...
   <div style="width: 100%">Testo inserito</div>
   ...
</div>

L’effetto è quello voluto:

Testo inserito

Ma se volessimo definire un padding di qualche pixel all’interno del div interno? Cosa accadrebbe? Forse qualcuno avrà già intuito il problema, ma partiamo direttamente dal risultato:

Testo inserito

Il <div> ha sbordato! E, per la precisione, di 20 pixel (è stato messo un padding di 10 pixel). Tutto questo, a ben pensarci, è ovvio: l’indicazione width: 100% si riferirà all’area del contenuto, ed è quella che sarà definita larga quanto l’elemento contenitore. La definizione del padding aggiunge in sostanza uno spessore esterno a quest’area, ed il risultato è così spiegato. Col box model di Microsoft, invece, non avremmo avuto questo problema.

Inutile pensarci su: non c’è alcun modo di risolvere questo problema, almeno con il solo uso dei fogli di stile. Se abbiamo usato le percentuali, saremo costretti a mettere dei padding con le percentuali, sottraendo il loro valore da quello di width. L’unico modo per ottenere l’effetto desiderato è quello di calcolare trmite JavaScript la larghezza in pixel dell’elemento contenitore ed in base a questo definire la larghezza del nostro <div> interno. Bella rogna, eh? Tutto per un effetto così semplice. E la cosa diventa ancora più ridicola se magari l’elemento contenitore ha una larghezza variabile e modificabile dall’utente (ad esempio, in un sito la cui struttura è stata definita con le percentuali): per ottenere un effetto di fluidità, bisogna effettuare un ricalcolo “al volo” delle dimensioni, con evidenti rallentamenti in caso di computer più vecchi (o di uno smartphone), quando un lavoro del genere è tipico del motore di rendering del browser, solitamente ben più efficiente.

Un altro esempio potrebbe essere, per l’appunto, quello di un sito la cui struttura è stata sviluppata usando le percentuali. Se volessimo avere, ad esempio, due sezioni larghe al 50% una accanto all’altra, non potremmo metterci né bordi, né padding, perché alla fine risulterebbero più larghe della metà della pagina e quindi la seconda andrebbe “a capo” (cioè sotto la prima).

Nel 2001, Internet Explorer 6 venne pubblicato e garantì il supporto al box model definito dal W3C, a patto che venisse messa una dichiarazione <!DOCTYPE> all’inizio del documento; nel caso contrario, usava il box model tradizionale di casa Microsoft. Gran parte dei webmaster si adeguarono al W3C, ma almeno da questo punto di vista fu una gran perdita. Tant’è vero che ora il W3C sembra essere tornato sui suoi passi, o quantomeno è intenzionato a lasciare la libertà agli sviluppatori di decidere che box model usare: con le nuove specifiche CSS3, infatti, verrà introdotta la proprietà box-sizing, che potrà assumere i valori content-box (di default, per lo standard W3C) o border-box (per il box model tradizionale).

Purtroppo, le specifiche del CSS3 sembrano non diventare mai definitive, ed ogni volta la loro uscita viene ritardata. Mentre le specifiche CSS1 vennero rilasciate nel 1996 e quelle CSS2 nel 1998, le specifiche CSS3 videro cominciare il loro sviluppo nel 1999 ma a distanza di ben 11 anni ancora non sono definitive. Fortunatamente, il W3C pubblica di continuo i lavori di sviluppo e molti browser si sono già adattati ad usare parte delle nuove specifiche già prima del tempo (col rovescio della medaglia per cui talvolta i risultati sono errati o fuori standard). In particolare, box-sixing (col prefisso -moz-) è stata adottata da Firefox già dalla versione 1.0, da Safari (col prefisso -webkit-) dalla versione 3, da Chrome (sempre con -webkit-), da Opera dalla versione 8.5 e Internet Explorer 8 (questi due senza prefissi). Ironicamente, quindi, proprio Internet Explorer è stato l’ultimo browser a seguire il “ritorno alle origini”…

Se avete uno dei suddetti browser, il risultato dovrebbe assomigliare a questo:

Questo è il codice relativo:

<div>
   ...
   <div style="width: 100%; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box;">Testo inserito</div>
   ...
</div>

Si noti la specifica multipla della proprietà per Firefox e Safari/Chrome: in futuro non sarà più necessaria.

Nota: per modificare la proprietà con JavaScript, si agisce sulla proprietà boxSizing dell’oggetto di stile (MozBoxSizing in Firefox, WebkitBoxSizing in Safari/Chrome). Si noti che, sebbene sia uguale a “” e non a undefined, in Internet Explorer 7 la proprietà non è supportata!

Annunci

3 luglio 2010

addEvent: una (altra) soluzione / a(nother) solution

Filed under: JavaScript — MaxArt @ 14:36
Tags: ,
English Version English version 

Una delle differenze più importanti tra il codice JavaScript che si scrive per Internet Explorer e quello per un browser aderente alle direttive W3C consiste nei metodi che si usano per definire un evento su un elemento della struttura della pagina. Mentre Internet Explorer usa i metodi attachEvent e removeEvent, gli standard indicano la strada in addEventListener e removeEventListener, rispettivamente, come funzioni per aggiungere e togliere un evento. Al di là delle differenze di funzionamento degli eventi tra i browser (Internet Explorer non supporta l’event capturing, motivo per il quale, del resto, è una tecnica poco sfruttata), i due metodi fanno pressoché la stessa cosa. Ma questo non vuol dire che fanno la stessa cosa.

Quello che si vorrebbe, magari, è una funzione che possa utilizzare l’uno o l’altro metodo in automatico a seconda del supporto che viene fornito dal browser. Una soluzione largamente adottata fu fornita da Scott Andrew LePera oltre 9 anni fa:

function addEvent(obj, evType, fn, useCapture){
  if (obj.addEventListener){
    obj.addEventListener(evType, fn, useCapture);
    return true;
  } else if (obj.attachEvent){
    var r = obj.attachEvent("on"+evType, fn);
    return r;
  } else {
    alert("Handler could not be attached");
  }
}

Questa semplice soluzione, insieme con la controparte removeEvent, funziona discretamente fino ad un certo punto. La differenza tra addEventListener e attachEvent consiste nel fatto che, nella funzione passata come gestore dell’evento, secondo gli standard la parola chiave this deve riferirsi, come logica vorrebbe, all’elemento HTML su cui è definito l’evento, ma in Internet Explorer this fa riferimento all’oggetto window. Una bella seccatura!

Un primo approccio alla soluzione

Una soluzione consiste nell’usare un wrapping del gestore dell’evento nel caso di Internet Explorer:

...
  } else if (obj.attachEvent){
    var r = obj.attachEvent("on"+evType, function() {fn.call(obj)});
  ...

Grazie all’uso di call, siamo sicuri che this all’interno della funzione farà riferimento all’elemento su cui è definito l’evento e non all’oggetto window. Basta questo? Macché! Questo può bastare finché gli eventi così definiti non debbano essere in seguito eliminati con removeEvent, perché il codice relativo è questo:

  obj.detachEvent("on"+evType, fn);

Ebbene, la rimozione dell’evento non funzionerà, perché si cercherà di rimuovere la funzione fn come gestore, ma non è tale funzione ad essere stata definita come gestore dell’evento, bensì il suo wrapper function() {fn.call(obj)}! Dovremmo quindi dare il riferimento al wrapper della funzione, ma questo ci è impossibile una volta usciti dalla funzione addEvent.

L’idea di John Resig

A tale proposito, il guru Peter-Paul Koch, autore del fenomenale (per quantità di risorse) sito quirksmode.org, indispensabile per aiutare tutti gli sviluppatori del web a districarsi tra le differenze di comportamento dei più comuni browser, nel 2005 indisse un concorso per definire un’accoppiata di funzioni addEvent/removeEvent che risolvesse la questione di this in Internet Explorer.

Il concorso fu vinto da un altro guru, John Resig, l’autore della notissima libreria jQuery. Questo è il codice che ha usato:

function addEvent( obj, type, fn ) {
  if ( obj.attachEvent ) {
    obj['e'+type+fn] = fn;
    obj[type+fn] = function(){obj['e'+type+fn]( window.event );}
    obj.attachEvent( 'on'+type, obj[type+fn] );
  } else
    obj.addEventListener( type, fn, false );
}
function removeEvent( obj, type, fn ) {
  if ( obj.detachEvent ) {
    obj.detachEvent( 'on'+type, obj[type+fn] );
    obj[type+fn] = null;
  } else
    obj.removeEventListener( type, fn, false );
}

Comincio col dire che la soluzione, ovviamente, funziona benissimo. Anche in questo caso si va costruire un wrapper della funzione originale (obj['e'+type+fn]), che viene però richiamato come funzione locale. Ho però alcune osservazioni a riguardo:

  • le funzioni eseguono un controllo sulla presenza di attachEvent/detachEvent ogni volta che vengono chiamate: questo è superfluo, perché è chiaro se tali metodi saranno presenti una volta, lo saranno sempre (non è che il motore JavaScript cambia nel bel mezzo dell’esecuzione!)
  • in ogni caso viene effettuato prima un controllo su attachEvent e non su addEventListener, come sarebbe più logico (prima gli standard);
  • mi è sempre parso un metodo piuttosto invasivo, che “sporca” l’elemento HTML con la definizione di attributi (perché così vengono considerati da Internet Explorer i membri definiti gli elementi HTML) con nomi assurdamente lunghi, con garanzia di unicità non completa e per giunta non vengono ripuliti del tutto da removeEvent.

Si tratta, si badi, di argomentazioni abbastanza secondarie, se non la prima che può avere un certo marginale impatto prestazionale. Tuttavia, credo che si possano risolvere in toto.

Una nuova soluzione

Questa è l’alternativa che propongo:

if (document.addEventListener) {
  addEvent=function addEvent(obj, type, fn) {obj.addEventListener(type, fn, false);}
  removeEvent=function removeEvent(obj, type, fn) {obj.removeEventListener(type, fn, false);}
} else {
  addEvent = function addEvent(obj, type, fn) {
    fn[type+fn] = function() {return fn.call(obj, window.event);};
    obj.attachEvent("on"+type, fn[type+fn]);
  };
  removeEvent = function removeEvent(obj, type, fn) {obj.detachEvent("on"+type, fn[type+fn]);}
}

La differenza con la soluzione di John Resig sta nel fatto che la mia versione di addEvent va a salvare il riferimento al wrapper non sull’oggetto elemento HTML, ma sull’oggetto fn che viene passato come argomento. In questo modo, removeEvent sa sempre dove andare a prendere il wrapper e rimuoverlo.

Posso dire di essere piuttosto sorpreso di aver sviluppato qualcosa che mi sia piaciuto più della versione di un mostro sacro come John Resig, ed ogni tanto mi chiedo se ciò che ho scritto non abbia delle controindicazioni a me non visibili. Finora, però, non m’è parso.


addEvent: a(nother) solution


One of the main differences between the JavaScript code you write per Internet Explorer and the one for a W3C standard compliant browser is about the methods you use to define an event on an element of the page structure. While Internet Explorer uses the methods attachEvent and removeEvent, the standards have their way in addEventListener and removeEventListener, rispectively, ad function to add and remove an event. Leaving apart the working differences among browsers (Internet Explorer doesn’t support event capturing, and that’s actually the reasing why this technique is not used often), these two methods almost do the same thing. But this doesn’t mean that they do the same thing.

What we actually want is a function that calls one method or the other automatically, depending on the browser support. A widely used solution was given by Scott Andrew LePera more than 9 years ago:

function addEvent(obj, evType, fn, useCapture){
  if (obj.addEventListener){
    obj.addEventListener(evType, fn, useCapture);
    return true;
  } else if (obj.attachEvent){
    var r = obj.attachEvent("on"+evType, fn);
    return r;
  } else {
    alert("Handler could not be attached");
  }
}

This simple solution, together with the removeEvent counterpart, works fairly well under certain conditions. The difference between addEventListener and attachEvent is in the fact that, while running the function passed as event listener, by the standards the key word this must refer, as logic should hint, to the HTML element on which the event is defined, but on Internet Explorer this always refers to the window object. A real pain!

<h3A first approach to the solution

A solution consists of the using of a wrapper of the event listener in the Internet Explorer case:

...
  } else if (obj.attachEvent){
    var r = obj.attachEvent("on"+evType, function() {fn.call(obj)});
  ...

Thanks to call, we’re sure that this inside the function will refer to the element on which the event is defined and not to the window object. Is that enough? Not at all! This can be enough until the events defined in this way are not to be removed later by removeEvent, because this is the relative part:

  obj.detachEvent("on"+evType, fn);

Therefore, removing the event will not work, because it will try to remove the function fn as the listener, but that not the function defined as the event listener, but rather it’s its wrapper function() {fn.call(obj)}! So we should give the reference to the function wrapper, but that’s impossible once we’ve returned from the addEvent function.

John Resig’s idea

By this way, guru Peter-Paul Koch, author of the fantastic (for resource quantity) site quirksmode.org, essential to help web developers to have their way among behaviour differences of the most common browsers, in 2005 called a contest to define an addEvent/removeEvent functions couple to solve the this problem in Internet Explorer.

Not surprisingly, the contest was won by another guru, John Resig, author of the arch-famous library jQuery. This is the code he used:

function addEvent( obj, type, fn ) {
  if ( obj.attachEvent ) {
    obj['e'+type+fn] = fn;
    obj[type+fn] = function(){obj['e'+type+fn]( window.event );}
    obj.attachEvent( 'on'+type, obj[type+fn] );
  } else
    obj.addEventListener( type, fn, false );
}
function removeEvent( obj, type, fn ) {
  if ( obj.detachEvent ) {
    obj.detachEvent( 'on'+type, obj[type+fn] );
    obj[type+fn] = null;
  } else
    obj.removeEventListener( type, fn, false );
}

Let’s start to say that the solution, obviously, works just fine. In this case, too, we build a wrapper of the original function (obj['e'+type+fn]), which is instead called as a “local” method. I have some notes about it:

  • those functions checks the presence of addEvent/removeEvent every time they’re called: this is redundant, because it’s obvious that if those methods are definded once, they always are (you don’t change the JavaScript engine in the middle of the execution!)
  • anyway, attachEvent is checked first, not addEventListener, as logic would suggest (standards first);
  • it always seemed to me an obtrusive way, that “spoils” the HTML element defining attributes (because it’s the way Internet Explorer deals with members defined on HTML elements) with ridicously long names, without the complete uniqueness warranty and, moreover, not entirely cleaned by removeEvent.

These are, mind you, quite minor arguments, with the exception of the first one which can have some marginal performance influence. Anyway, I think that they can be completely solved.

A new solution

This is the alternative I propose:

if (document.addEventListener) {
  addEvent=function addEvent(obj, type, fn) {obj.addEventListener(type, fn, false);}
  removeEvent=function removeEvent(obj, type, fn) {obj.removeEventListener(type, fn, false);}
} else {
  addEvent = function addEvent(obj, type, fn) {
    fn[type+fn] = function() {return fn.call(obj, window.event);};
    obj.attachEvent("on"+type, fn[type+fn]);
  };
  removeEvent = function removeEvent(obj, type, fn) {obj.detachEvent("on"+type, fn[type+fn]);}
}

The difference with the solution of John Resig is that my version of addEvent saves the wrapper reference not on the HTML element but rather on the fn object that is passed as argument. This way, removeEvent always knows where to get the wrapper and remove it.

I must say I’m quite surprised to have developed something that I liked more than the version of a sacred genius as John Resig, and sometimes I wonder if what I wrote has some issues that I don’t see. It didn’t seem to me… so far.

29 giugno 2010

forEach, map e gli altri

Filed under: JavaScript — MaxArt @ 12:32
Tags: ,
Indice:

Molto spesso vi sarà capitato, programmando in JavaScript, di avere un array e di doverlo “scorrere” per vari motivi, come quello di cercare un elemento particolare, oppure di creare un nuovo array dai suoi elementi, o ancora di eseguire una serie di istruzioni per ognuno degli elementi. Il modo classico di agire è piuttosto diretto:

for (var i=0; i<a.length; i++) {
   ...
   if (a[i]===test) ...
   ...
}

Questo modo di agire è piuttosto banale. Anzi, direi troppo, da risultare alquanto noioso e banale da implementare. A questo sembrano aver pensato gli sviluppatori di Mozilla quando hanno presentato, nelle loro estensioni di JavaScript 1.6, alcune funzionalità aggiuntive al prototype dell’oggetto Array. Vediamole nel dettaglio:

  • forEach: esegue un’azione per ognuno degli elementi dell’array;
  • map: restituisce un nuovo array costruito a partire degli elementi dall’array iniziale;
  • filter: restituisce un nuovo array formato dai soli elementi che rispettano una data condizione
  • every: restituisce un valore booleano se tutti gli elementi dell’array rispettano una certa condizione.
  • some: restituisce un valore booleano se almeno un elemento dell’array rispetta una certa condizione.
  • indexOf: restituisce la posizione di un elemento all’interno di un array (-1 se non è presente);
  • lastIndexOf: come sopra, ma partendo dal fondo dell’array

Si tratta di metodi molto comodi ma, come vedremo in seguito, da usare con la dovuta cautela perché pagano un certo scotto prestazionale.

Funzionamento Torna in cima

Come funzionano questi metodi? Prendiamo un esempio con map:

var a=[1,2,3,4,5];
var b=a.map(function(v) {return v*v;}); // b=[1,4,9,16,25];

Quello che si deve fornire a map è una funzione, che accetti come argomento l’elemento dell’array, e che restituisca un valore elaborato per costruire un nuovo array. In questo caso, è una banale funzione che restituisce il quadrato del numero. Non dobbiamo più cioè fare una roba del genere:

var a=[1,2,3,4,5];
for (var i=0, b=new Array(a.length); i<a.length; i++) {
   b[i]=a[i]*a[i];
}

Non dobbiamo più tirarci indietro la variabile i ed indicizzare. Basta con incrementi, confronti e inizializzazioni di cicli for: fa tutto map, passando comodamente l’elemento alla funzione.

L’unica cosa seccante è dover definire una funzione ogni volta, ma la cosa sarebbe stata ancora più banale se, al posto del quadrato del numero, avessimo voluto la radice quadrata, nel qual caso avremmo potuto semplicemente fare:

b=a.map(Math.sqrt);

A volte, però, l’indice dell’elemento può tornarci utile. Niente paura, perché map alla funzione indicata passa non solo l’elemento, ma appunto anche il suo indice e pure il puntatore all’array stesso:

var a=[1,2,3,4,5];
var b=a.map(function(v, i, a) {return v+i+a.length;}); // b=[6,8,10,12,14]

Inoltre, come secondo argomento di map, è possibile passare un oggetto che, all’interno della funzione, può essere referenziato con la parola this, che normalmente fa riferimento all’oggetto window.

In maniera del tutto simile agisce anche forEach, che però non restituisce alcun array e dunque non è necessario che la funzione passata restituisca un valore. Per metodi filter, every e some, invece, la funzione deve restituire un valore vero o falso (ma non necessariamente true o false) a seconda che l’elemento rispetti o meno una data condizione definita dall’utente. Allora filter restituirà un array composto solo dagli elementi che soddisfano la condizione, mentre every e some dicono (restituendo un valore booleano) rispettivamente se tutti o almeno un elemento rispetta la condizione.

Si badi che forEach, map, filter, every e some chiamano la funzione argomento solo l’elemento è effettivamente definito (e questo vuol dire che può assumere anche il valore undefined, ma l’elemento dev’essere proprio definito come… undefined!). La cosa si nota nel seguente esempio:

var a=new Array(5); // a.length=5
var b=a.map(function() {return 0}); // b=[]

Ci saremmo potuti aspettare che b diventasse un array di 5 zeri, ed invece rimane vuoto, nonostante a sia “lungo” 5. Usando un ciclo for e confrontando con la lunghezza di a, avremmo avuto il nostro array di 5 zeri: si tenga a mente questa differenza.

Altri metodi molto utili sono gli analoghi delle stringhe indexOf e lastIndexOf: essi agiscono in maniera del tutto simile ai metodi delle stringhe, cercando l’elemento indicato come argomento scorrendo array indice per indice, e restituendo -1 se l’elemento non è presente nell’array. Come secondo argomento, si può fornire l’indice da cui partire (che può anche essere negativo, ma con alcune limitazioni che mi lasciano un po’ perplesso). Esempi:

var a=["a","e","i","o","u","a","e"];
var b=a.indexOf("i");       // b=2
var b=a.indexOf("i",3);     // b=-1
var b=a.indexOf("a");       // b=0
var b=a.lastIndexOf("a");   // b=5
var b=a.lastIndexOf("a",3); // b=0

Quando si possono usare? Torna in cima

Come detto, queste novità sono state introdotte dalla Mozilla Foundation con il loro sviluppo a JavaScript 1.6: questo vuol dire supporto per tutti i browser con motore Gecko 1.8b2 e successivi. In parole povere, Firefox 1.5 e successivi (e qualche altro browser).

Tuttavia, queste estensioni sono state velocemente adottate anche dagli altri browser, come Chrome, Safari e Opera. L’unico importante browser che rimane fuori è, in effetti, il solo Internet Explorer, anche nella versione 8. E non sarebbe neanche scorretto, visto che non si tratta di estensioni standard (se non in ECMAScript versione 5, uscita solo pochi mesi fa).

Per compatibilità con gli altri browser, si può estendere preventivamente l’oggetto Array.prototype, nei modi scritti molto chiaramente nelle pagine della Mozilla Foundation:

Una volta introdotte le istruzioni in questo modo, anche Internet Explorer le supporterà in maniera del tutto identica a Firefox e agli altri browser (con qualche scotto da pagare in termini di prestazioni, come sempre per Internet Explorer).

Un trucchetto Torna in cima

Queste estensioni sono già abbastanza belle di per sé, ma forse qualcuno avrà notato che, una volta iniziato il ciclo con forEach o con map, non è possibile interromperlo, come invece spesso vorremmo. In effetti, queste funzioni sono utili soprattutto se la condizione del ciclo for che useremmo al loro posto è il semplice confronto con la lunghezza dell’array, e nient’altro.

Tuttavia, almeno per forEach, si può fare qualcosa: si userà al suo posto some, e la funzione passata, oltre a fare tutte le operazioni che vogliamo, restituirà un valore “vero” quando vogliamo che s’interrompa il ciclo. Si noti quest’esempio:

var a=[1,1,2,3,5,8,13,21,34,55], t=0;

// Questo è il ciclo for che useremmo di solito
for (var i=0; i<a.length; i++) {
   if (a[i]>10) break;
   document.write(a[i]+"\n");
   t++;
} // t=6

// Usando forEach, dobbiamo complicare la funzione, che verrà chiamata
// inutilmente per diverse altre volte (si noti la condizione dell'if)
a.forEach(function(v) {
   if (v<=10) document.write(v+"\n");
   t++;
}); // t=10

// Con some, invece, il ciclo viene interrotto prima
a.some(function(v) {
   if (v>10) return true;
   document.write(v+"\n");
   t++;
}); // t=6

Con qualche accorgimento, si può usare every anziché some. Per avere un comportamento simile con map, le cose si complicano un pelo e soprattutto la differenza col buon vecchio ciclo for diventa esigua:

var a=[1,1,2,3,5,8,13,21,34,55];

// Algoritmo che useremmo di solito
var b=[];
for (var i=0; i<a.length; i++) {
   if (a[i]>10) break;
   b[i]=a[i]*a[i];
}

// Algoritmo con some
var b=[];
a.some(function(v, i) {
   if (v>10) return true;
   b[i]=v*v;
});

Prestazioni Torna in cima

Quando si vanno ad esaminare le prestazioni, come avevo accennato, sorgono i problemi. Facendo alcuni test con vari browser, sotto le medesime condizioni ho provato a confrontare i seguenti costrutti equivalenti:

var a=new Array(1000);
for (var i=0; i<a.length; i++) a[i]=0; // Inizializzazione di a;

// Ciclo for
for (var i=0; i<a.length; i++) a[i]=a[i]*i*Math.random();

// map
a.map(function(v, i) {return v*i*Math.random()});

Ebbene, i confronti sono impietosi. Eseguendo qualcosa come 4000 iterazioni con map, questi sono i risultati sui vari browser:

  • Firefox 3.6.6: 2459 ms
  • Chrome 6.0.427.0: 463 ms
  • Opera 10.54: 3266 ms
  • Internet Explorer 7/8: circa 15000 ms

Usando invece il semplice ciclo for, i tempi sono talmente brevi da risultare non rilevabili, in tutti i browser (incluso Internet Explorer). Come mai questo fatto?

A ben pensarci, la cosa è semplice: con map ad ogni ciclo viene effettuata una chiamata alla funzione che viene passata, e questa è un’operazione piuttosto costosa a livello di prestazioni. Il mio consiglio, quindi, è quello di usare forEach e gli altri metodi solo quando:

  • la chiamata alla funzione è temporalmente molto più breve rispetto all’esecuzione della funzione stessa;
  • il ciclo è di lunghezza breve, al massimo qualche migliaio di iterazioni;
  • il tempo di esecuzione non è così importante (nei casi, ad esempio, di dipendenza dall’utente)
  • quando si stende una versione preliminare del codice, per velocità di scrittura, procedendo in un secondo momento a sostituire i metodi con dei cicli for.

Detto questo, buon codice a tutti!

28 giugno 2010

Ciao mondo!!

Filed under: Senza categoria — MaxArt @ 09:19
Beh, che dire? Anche questo blog lo inizio con il titolo “Ciao mondo!”? In effetti, trattandosi di un blog di carattere informatico, dovrebbe essere la soluzione migliore. Ora spiego di cosa si dovrebbe occupare questo sito.

Da circa un anno mi sto occupando di sviluppo web, lato client (soprattutto) e qualcosa lato server. Partendo da zero o quasi (conoscendo HTML e avendo disperse nella memoria le nozioni di JavaScript), credo di aver imparato una discreta quantità di cose, affinato grandemente il mio modo di programmare, avuto idee valide. Questo blog è dedicato a chi intende sviluppare per il web, partendo da una certa base di conoscenze HTML, CSS e JavaScript.

L’obiettivo è lo sviluppo di soluzioni web originali, partendo cioè dagli strumenti di base che mette a disposizione ogni browser o http server, senza far quindi uso di librerie come jQuery. Verranno sviluppati alcuni strumenti “di base” molto comuni, che potrei usare sistematicamente nella scrittura del codice, come ad esempio la funzione $ (dollaro) definita come

function $(obj) {
	return typeof obj==="string" ? document.getElementById(obj) : obj;
}

Spero che il significato sia chiaro. Si tratta di una funzione molto semplice, che rallenta ben poco l’esecuzione del codice ma che soprattutto aiuta molto nella stesura del codice, evitando di digitare un bel po’ di caratteri ogni volta che si cerca un elemento col suo id.

Saranno affrontati i problemi di compatibilità tra browser e risolti quando possibile. Saranno affrontate tematiche non solo strettamente programmatorie, ma anche delle direzioni dello sviluppo del web in generale. Probabilmente tradurrò qualche articolo in inglese, semmai volessi avere pareri anche da una comunità più vasta di quella italiana.

Non credo di dover aggiungere altro, dunque… buona lettura!

Crea un sito o un blog gratuitamente presso WordPress.com.