Introduzione

Introduzione

JavaScript Garden è una collezione in continua crescita di documentazione relativa alle parti più peculiari del linguaggio di programmazione JavaScript. Il suo intento è quello di mostrare come evitare i più comuni errori, i problemi legati alla performance e le cattive abitudini che i programmatori JavaScript non esperti possono incontrare lungo il loro cammino di approfondimento del linguaggio.

L'obiettivo di JavaScript Garden non è quello di insegnarti JavaScript. Una conoscenza pregressa del linguaggio è fortemenete consigliata, in modo da capire gli argomenti trattati da questa guida. Per poter imparare le basi del linguaggio, ti suggeriamo di leggere l'eccellente guida su Mozilla Developer Network.

Gli autori

Questa guida è il risultato del lavoro di due utenti di Stack Overflow, Ivo Wetzel (stesura) e Zhang Yi Jiang (progettazione).

È attualmente mantenuto da Tim Ruffles.

Collaboratori

Hosting

JavaScript Garden è ospitato su GitHub, ma Cramer Development ci supporta con un mirror su JavaScriptGarden.info.

Licenza

JavaScript Garden è pubblicato sotto la licenza MIT ed ospitato su GitHub. Se trovi inesattezze o errori di battitura, ti prego di segnalare il problema o fare un pull request sul nostro repository. Puoi anche trovarci nella stanza JavaScript della chat di Stack Overflow.

Oggetti

Utilizzo di oggetti e proprietà

Tutto in JavaScript funziona come un oggetto, con la sola eccezione di null e undefined.

false.toString(); // 'false'
[1, 2, 3].toString(); // '1,2,3'

function Foo(){}
Foo.bar = 1;
Foo.bar; // 1

Un'idea comunemente errata è che i numeri letterali non possano essere usati come oggetti. Questo a causa di una scorretta gestione da parte del parser di JavaScript, che tenta di analizzare la dot notation di un numero come se fosse un letterale in virgola mobile.

2.toString(); // solleva SyntaxError

Esistono un paio di soluzioni che possono essere usate per far sì che i numeri letterali vengano considerati come oggetti.

2..toString(); // il secondo punto viene correttamente riconosciuto
2 .toString(); // notate lo spazio tra il numero e il punto
(2).toString(); // viene prima valutato 2

Oggetti come un tipo di dato

Gli oggetti in JavaScript possono anche essere usati come tabelle hash e consistono principalmente di proprietà con un nome che mappano dei valori.

Usando un oggetto letterale (notazione {}) è possibile creare un semplice oggetto. Questo nuovo oggetto eredita da Object.prototype e non ha proprietà definite.

var foo = {}; // un nuovo oggetto vuoto

// un nuovo oggetto con una proprietà `test` con valore 12
var bar = {test: 12};

Accedere alle proprietà

È possibile accedere alle proprietà di un oggetto in due modi. Usando il punto oppure attraverso l'uso delle parentesi quadre.

var foo = {name: 'kitten'}
foo.name; // kitten
foo['name']; // kitten

var get = 'name';
foo[get]; // kitten

foo.1234; // SyntaxError
foo['1234']; // funziona

Le due notazioni funzionano quasi in modo identico, con la sola differenza che usando le parentesi quadre è possibile impostare dinamicamente le proprietà ed il loro nome identificatore, cosa che altrimenti genererebbe un errore di sintassi.

Cancellazione delle proprietà

Il solo modo per rimuovere una proprietà da un oggetto è quello di usare l'operatore delete. Impostando la proprietà a undefined o null, infatti, si rimuove solo il valore associato alla proprietà, ma non la chiave.

var obj = {
    bar: 1,
    foo: 2,
    baz: 3
};
obj.bar = undefined;
obj.foo = null;
delete obj.baz;

for(var i in obj) {
    if (obj.hasOwnProperty(i)) {
        console.log(i, '' + obj[i]);
    }
}

Il codice qui sopra, stamperà sia bar undefined che foo null. Soltanto baz è stato rimosso, e quindi non compare nell'output.

Notazione delle chiavi

var test = {
    'case': 'Parola chiave, scrivimi come stringa',
    // solleva SyntaxError
    delete: 'Parola chiave, anche io devo essere una stringa'
};

Le proprietà di un oggetto possono essere scritte sia come normali caratteri che come stringhe. A causa di un altro errore di progettazione del parser di JavaScript, il codice appena visto genererà un SyntaxError in ECMAScript precedente alla versione 5.

Questo errore nasce dal fatto che delete è una parola chiave, quindi, deve essere scritta come una stringa letterale per assicurarsi che venga correttamente interpretata dai vecchi motori JavaScript.

Il prototipo

JavaScript non segue il classico modello di ereditarietà ma, piuttosto, utilizza quello prototipale.

Anche se ciò viene considerato come uno dei punti più deboli del JavaScript, il modello ad ereditarietà prototipale è difatto più potente di quello classico. Ad esempio, è piuttosto semplice creare un modello classico sulle basi di quello prototipale, mentre l'operazione inversa è piuttosto complessa.

JavaScript è il solo linguaggio ampiamente utilizzato che sfrutta l'ereditarietà prototipale, quindi è possibile prendersi il proprio tempo per adeguarsi alle differenze esistenti tra i due modelli.

La prima grande differenza è che l'ereditarietà in JavaScript utilizza le catene di prototipi.

function Foo() {
    this.value = 42;
}
Foo.prototype = {
    method: function() {}
};

function Bar() {}

// Imposta il prototipo di Bar ad una nuova istanza di Foo
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';

// Si assicura di elencare Bar come l'attuale costruttore
Bar.prototype.constructor = Bar;

var test = new Bar(); // crea una nuova istanza di bar

// La catena di prototipi finale
test [istanza di Bar]
    Bar.prototype [istanza di Foo]
        { foo: 'Hello World', value: 42 }
        Foo.prototype
            { method: ... }
            Object.prototype
                { toString: ... /* ecc. */ }

Nel codice qui sopra, l'oggetto test erediterà sia da Bar.prototype che da Foo.prototype, e quindi avrà accesso alla funzione method che era stata definita in Foo. Avrà anche accesso alla proprietà value dell'unica istanza di Foo, cioè il suo prototipo. È importante notare come new Bar() non crei una nuova istanza di Foo, ma piuttosto riutilizzi quella assegnata al suo prototipo. Perciò, tutte le istanze di Bar condivideranno la stessa proprietà value.

Tabella delle proprietà

Quando si accede alle proprietà di un oggetto, JavaScript risale la catena di prototipi fino a che non incontra una proprietà con il nome richiesto.

Se raggiunge la cima della catena (cioè Object.prototype) senza aver trovato le specifica proprietà, ritorna il valore undefined.

La proprietà Prototype

Anche se la proprietà prototype viene usata dal linguaggio per creare la catena di prototipi, è comunque sempre possibile assegnarvi un qualsiasi dato valore. Nonostante cio, i dati primitivi verranno semplicemente ignorati quando assegnati ad un prototipo.

function Foo() {}
Foo.prototype = 1; // nessun effetto

L'assegnazione di oggetti, come mostrato nell'esempio precedente, funzionerà, e permette la creazione dinamica di catene di prototipi.

Performance

Il tempo di ricerca per proprietà presenti in alto (all'inizio) della catena di prototipi, può avere un impatto negativo sulla performance, e questo deve essere tenuto bene in considerazione in codice dove la performance è un fattore critico. Inoltre, il tentativo di accedere a proprietà inesistenti obbligherà comunque ad attraversare tutta la catena di prototipi.

Oltre a ciò, iterando tra le proprietà di un oggetto, ogni proprietà presente nella catena di prototipi verrà enumerata.

Estensione di prototipi nativi

Una caratteristica che viene spesso abusata, è quella di estendere Object.prototype o uno degli altri prototipi interni al linguaggio.

Questa tecnica viene detta monkey patching e vìola il principio di incapsulamento. Anche se usata da popolari framework come Prototype, non c'è una valida ragione per pasticciare, aggiungendo ai tipi interni del linguaggio funzionalità non standard.

La sola buona ragione per estendere un prototipo interno è quella di effettuare il backport di funzionalità presenti nei motori JavaScript più recenti, come ad esempio Array.forEach.

In conclusione

È essenziale capire il modello di ereditarietà prototipale prima di scrivere codice complesso che ne faccia uso. Bisogna, inoltre, tenere sotto controllo la lunghezza della catena di prototipi nel proprio codice, e suddividerla in più catene se necessario, per evitare possibili problemi di performance. Inoltre, i prototipi nativi non dovrebbero mai essere estesi a meno che non sia per garantire compatibilità con le funzionalità più recenti di JavaScript.

hasOwnProperty

Per verificare se un oggetto ha (possiede) una proprietà definita dentro se stesso piuttosto che in qualche parte della sua catena di prototipi, è necessario usare il metodo hasOwnProperty che tutti gli oggetti ereditano da Object.prototype.

hasOwnProperty è la sola cosa in JavaScript che si occupa delle proprietà senza attraversare la catena di prototipi.

// Modifichiamo Object.prototype
Object.prototype.bar = 1;
var foo = {goo: undefined};

foo.bar; // 1
'bar' in foo; // true

foo.hasOwnProperty('bar'); // false
foo.hasOwnProperty('goo'); // true

Solo hasOwnProperty darà il risultato atteso e corretto. Guarda la sezione [cicli for in][#object.forinloop] per maggiori dettagli riguardo a quando usare hasOwnProperty per iterare le proprietà di un oggetto.

hasOwnProperty come proprietà

JavaScript non protegge il nome di proprietà hasOwnProperty. Quindi, se esiste la possibilità che un oggetto possa avere una proprietà con questo nome, è necessario usare un hasOwnProperty esterno per ottenere il risultato corretto.

var foo = {
    hasOwnProperty: function() {
        return false;
    },
    bar: 'Here be dragons'
};

foo.hasOwnProperty('bar'); // ritorna sempre false

// Usa un altro hasOwnProperty di Object e lo richiama con 'this' impostato a foo
({}).hasOwnProperty.call(foo, 'bar'); // true

// E' anche possibile usare hasOwnProperty dal prototipo di
// Object per questo scopo
Object.prototype.hasOwnProperty.call(foo, 'bar'); // true

In conclusione

Usare hasOwnProperty è l'unico metodo affidabile per verificare l'esistenza di una proprietà in un oggetto. È raccomandabile che hasOwnProperty venga usata in molti casi in cui è necessario iterare le proprietà di un oggetto, come descritto nella sezione cicli for in.

Il ciclo for in

Come per l'operatore in, il ciclo for in attraversa la catena di prototipi quando itera tra le proprietà di un oggetto.

// Modifichiamo Object.prototype
Object.prototype.bar = 1;

var foo = {moo: 2};
for(var i in foo) {
    console.log(i); // stampa sia bar che moo
}

Dato che non è possibile modificare il comportamento del ciclo for in, è necessario filtrare le proprietà indesiderate all'interno del ciclo stesso. In ECMAScript 3 o precedente, questo può essere fatto usando il metodo hasOwnProperty di Object.prototype.

A partire da ECMAScript 5, Object.defineProperty può essere utilizzato con enumerbale impostato a false per aggiungere proprietà agli oggetti (incluso Object) senza che queste proprietà vengano enumerate. In questo caso è ragionevole assumere che, nel codice di un'applicazione, ogni proprietà enumerabile sia stata aggiunta per un motivo, ed quindi omettere hasOwnProperty in quanto rende il codice più prolisso e meno leggibile. Nel codice delle librerie hasOwnProperty dovrebbe essere ancora utilizzato, dato che non è possibile presumere quali proprietà enumerabili siano presenti nella catena dei prototipi.

Usare hasOwnProperty per il filtraggio

// questo è il foo dell'esempio precedente
for(var i in foo) {
    if (foo.hasOwnProperty(i)) {
        console.log(i);
    }
}

Questa è la sola versione corretta da usare con le vecchie versioni di ECMAScript. Proprio a causa dell'utilizzo di hasOwnProperty, soltanto moo verrà stampato; mentre omettendone l'uso, il codice sarà soggetto ad errori nei casi dove i prototipi nativi (ad esempio Object.prototype) sono stati estesi.

Nelle nuove versioni di ECMAScript, le proprietà non enumerabili possono essere definite con Object.defineProperty, riducendo il rischio di iterare sulle proprietà non usando hasOwnProperty. È altresì importante stare attenti quando si usano librerie come Prototype, che ancora non sfruttano le nuove funzionalità di ECMAScript. Quando questo framework viene incluso, è sicuro che i cicli for in che non utilizzano hasOwnProperty non funzioneranno.

In conclusione

Si raccomanda di usare sempre hasOwnProperty in ECMAScript 3 o precedenti, e nel codice delle librerie. Non si dovrebbe mai dare per scontato nell'ambiente in cui il codice sta girando, se i prototipi nativi sono stati estesi o meno. A partire da ECMAScript 5 Object.defineProperty rende possibile definire proprietà non enumerabili ed omettere hasOwnProperty nel codice dell'applicazione.

Funzioni

Dichiarazioni ed espressioni di funzione

Le funzioni in JavaScript sono oggetti di prima classe. Ciò significa che possono essere usate come ogni altro valore. Un uso comune di questa caratteristica è quello di passare una funzione anonima come funzione di callback ad un'altra funzione, possibilmente asincrona.

La dichiarazione di function

function foo() {}

La funzione qui sopra viene elevata (hoisted) prima che inizi l'esecuzione del programma. Questo vuol dire che essa è disponibile da un qualsasi punto dello scope in cui è stata definita, anche se richiamata prima dell'effettiva definizione nel sorgente.

foo(); // funziona perché foo è stata creata prima di eseguire il codice
function foo() {}

L'espressione function

var foo = function() {};

Questo esempio assegna la funzione anonima alla variabile foo.

foo; // 'undefined'
foo(); // questo solleva un TypeError
var foo = function() {};

Dato che var è una dichiarazione che eleva il nome di variabile foo prima che l'esecuzione del codice inizi, foo è già dichiarata quando lo script viene eseguito.

Ma, dal momento che le assegnazioni avvengono solo a runtime, il valore di foo sarà undefined per default, prima che il relativo codice sia eseguito.

Espressione di funzione con nome

Un altro caso speciale è l'assegnazione di funzioni con nome.

var foo = function bar() {
    bar(); // funziona
}
bar(); // ReferenceError

Qui, bar non è disponibile nello scope più esterno, dal momento che la funzione viene assegnata solo a foo, mentre è disponibile all'interno di bar. Ciò è dato dal modo in cui funziona la risoluzione dei nomi in JavaScript: il nome della funzione è sempre reso disponibile nello scope locale della funzione stessa.

Come funziona this

JavaScript ha una concezione differente di ciò a cui il nome speciale this fa normalmente riferimento nella maggior parte degli altri linguaggi di programmazione. Ci sono esattamente cinque differenti modi nei quali il valore di this può essere associato nel linguaggio.

Lo scope globale

this;

Usando this nello scope globale, esso farà semplicemente riferimento all'oggetto globale.

Richiamando una funzione

foo();

Qui, this farà ancora riferimento all'oggetto globale.

Richiamando un metodo

test.foo();

In questo esempio, this farà riferimento a test.

Richiamando un costruttore

new foo();

Una chiamata di funzione che viene preceduta dalla parola chiave new agisce come un costruttore. Dentro la funzione, this farà riferimento all'Object appena creato.

Impostazione esplicita di this

function foo(a, b, c) {}

var bar = {};
foo.apply(bar, [1, 2, 3]); // l'array verrà espanso come mostrato sotto
foo.call(bar, 1, 2, 3); // risulterà in a = 1, b = 2, c = 3

Quando si usano i metodi call o apply di Function.prototype, il valore di this all'interno della funzione chiamata viene esplicitamente impostato al primo argomento della corrispondente chiamata di funzione.

Come risultato, nell'esempio sopra, il caso del metodo non viene applicato, e this all'interno di foo sarà impostato a bar.

Insidie comuni

Mentre molti di questi casi hanno senso, il primo può essere considerato un altro errore di progettazione del linguaggio perché non ha mai un uso pratico.

Foo.method = function() {
    function test() {
        // this viene impostato all'oggetto globale
    }
    test();
};

Una comune credenza è che this all'interno di test faccia riferimento a Foo mentre, invece, non è così.

Per poter ottenere l'accesso a Foo dall'interno di test, si può creare una variabile locale all'interno di method che faccia riferimento a Foo.

Foo.method = function() {
    var self = this;
    function test() {
        // Qui viene usato self invece di this
    }
    test();
};

self è solo un normale nome di variabile, ma viene comunemente usato come riferimento ad un this più esterno. Abbinato alle closures può anche essere usato per passare il valore di this.

Con l'introduzione di ECMAScript 5 è possibile usare il metodo bind combinato con una funziona anonima

Foo.method = function() {
    var test = function() {
        // this ora fa riferimento a Foo
    }.bind(this);
    test();
};

Metodi di asseganzione

Un'altra cosa che non funziona in JavaScript è la creazione di un alias ad una funzione, cioè l'assegnazione di un metodo ad una variabile.

var test = someObject.methodTest;
test();

A causa della prima dichiarazione, test ora agisce da semplice chiamata a funzione e quindi, this all'interno di essa non farà più riferimento a someObject.

Mentre l'assegnazione tardiva di this potrebbe sembrare una cattiva idea in un primo momento, alla prova dei fatti è ciò che fa funzionare l'ereditarietà prototipale.

function Foo() {}
Foo.prototype.method = function() {};

function Bar() {}
Bar.prototype = Foo.prototype;

new Bar().method();

Quando method viene chiamato da un'istanza di Bar, this farà riferimento a quell'istanza.

Closures e riferimenti

Una delle caratteristiche più potenti di JavaScript è la disponibilità delle closure. Con le closure, gli scope hanno sempre accesso allo scope più esterno nel quale sono state definite. Dal momento che il solo scope che JavaScript ha è lo scope di funzione, tutte le funzioni, per default, agiscono da closure.

Emulare variabili private

function Counter(start) {
    var count = start;
    return {
        increment: function() {
            count++;
        },

        get: function() {
            return count;
        }
    }
}

var foo = Counter(4);
foo.increment();
foo.get(); // 5

Qui, Counter ritorna due closure: la funzione increment e get. Entrambe mantengono un riferimento allo scope di Counter e, quindi, hanno sempre accesso alla variabile count definita in quello scope.

Perché le variabili private funzionano

Dato che non è possibile fare riferimento o assegnare scope in JavaScript, non c'è modo per accedere alla variabile count dall'esterno. Il solo modo per interagire con essa è tramite le due closure.

var foo = new Counter(4);
foo.hack = function() {
    count = 1337;
};

Il codice sopra non modificherà la variabile count nello scope di Counter, dato che foo.hack non è stato definito in quello scope. Invece, creerà (o meglio, sostituirà) la variabile globale count.

Closure nei cicli

Un errore che spesso viene fatto è quello di usare le closure all'interno dei cicli, come se stessero copiando il valore della variabile dell'indice del ciclo.

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}

Questo esempio non stamperà i numeri da 0 a 9, ma semplicemente il numero 10 dieci volte.

La funzione anonima mantiene un riferimento ad i, ma al momento in cui console.log viene richiamata, il ciclo for è già terminato, ed il valore di i è stato impostato a 10.

Per ottenere l'effetto desiderato, è necessario creare una copia del valore di i.

Evitare il problema del riferimento

Per copiare il valore della variabile indice del ciclo, è meglio usare un contenitore anonimo.

for(var i = 0; i < 10; i++) {
    (function(e) {
        setTimeout(function() {
            console.log(e);
        }, 1000);
    })(i);
}

La funzione anonima più esterna viene chiamata immediatamente con i come suo primo argomento e riceverà una copia del valore di i come suo parametro e.

La funzione anonima che viene passata a setTimeout ora ha un riferimento a e, il cui valore non viene modificato dal ciclo.

C'è anche un altro possibile modo per ottenere il medesimo risultato, e cioè ritornare una funzione dal contenitore anonimo che avrà quindi lo stesso comportamento del codice visto precedentemente.

for(var i = 0; i < 10; i++) {
    setTimeout((function(e) {
        return function() {
            console.log(e);
        }
    })(i), 1000)
}

C'è un ulteriore modo per ottenere ciò, usando .bind, che può assegnare un contesto this e degli argomenti ad una funzione. Esso funziona allo stesso modo degli esempi precedenti

for(var i = 0; i < 10; i++) {
    setTimeout(console.log.bind(console, i), 1000);
}

L'oggetto arguments

Ogni scope di funzione in JavaScript può accedere alla speciale variabile arguments. Questa variabile mantiene un elenco di tutti gli argomenti che sono stati passati alla funzione.

L'oggetto arguments non è un Array. Sebbene abbia in parte la semantica di un array (nello specifico la proprietà length), esso non eredita da Array.prototype ed è a tutti gli effetti un Object.

Proprio per questo motivo, non è possibile usare su arguments i metodi standard degli array come push, pop, slice. E mentre l'iterazione con un semplice ciclo for funzionerà senza problemi, sarà necessario convertire l'oggetto in un vero Array per poter usare i metodi standard di Array con esso.

Conversione ad array

Il codice seguente ritornerà un nuovo Array contenenente tutti gli elementi dell'oggetto arguments.

Array.prototype.slice.call(arguments);

Dato che questa conversione è lenta, non è raccomandato usarla in sezioni di codice in cui la performance è un fattore critico.

Passaggio di argomenti

Quello che segue è il metodo raccomandato per passare argomenti da una funzione ad un'altra.

function foo() {
    bar.apply(null, arguments);
}
function bar(a, b, c) {
    // codice da eseguire
}

Un altro trucco è quello di usare call e apply insieme per creare veloci contenitori senza vincoli.

function Foo() {}

Foo.prototype.method = function(a, b, c) {
    console.log(this, a, b, c);
};

// Crea una versione senza vincoli di "method"
// Richiede i parametri: this, arg1, arg2...argN
Foo.method = function() {

    // Risultato: Foo.prototype.method.call(this, arg1, arg2... argN)
    Function.call.apply(Foo.prototype.method, arguments);
};

Parametri formali e indici degli argomenti

L'oggetto arguments crea funzioni getter e setter sia per le sue proprietà che per i parametri formali della funzione.

Come risultato, la modifica del valore di un parametro formale modificherà anche il valore della corrispondente proprietà nell'oggetto arguments, e vice versa.

function foo(a, b, c) {
    arguments[0] = 2;
    a; // 2

    b = 4;
    arguments[1]; // 4

    var d = c;
    d = 9;
    c; // 3
}
foo(1, 2, 3);

Miti e verità sulla performance

Il solo caso in cui l'oggetto arguments non viene creato, è quando esso viene dichiarato come un nome all'interno di una funzione o uno dei suoi parametri formali. Non importa che venga usato o meno.

Sia i getter che i setter vengono sempre creati. Perciò, il loro utilizzo non ha praticamente alcun impatto sulle prestazioni, specialmente nel mondo reale dove nel codice c'è più di un semplice accesso alle proprietà dell'oggetto arguments.

Ad ogni modo, c'è un caso che ridurrà drasticamente la performance nei motori JavaScript moderni. È il caso dell'utilizzo di arguments.callee.

function foo() {
    arguments.callee; // fa qualcosa con questo oggetto funzione
    arguments.callee.caller; // e l'oggetto funzione chiamante
}

function bigLoop() {
    for(var i = 0; i < 100000; i++) {
        foo(); // normalmente sarebbe sostituito con il suo codice...
    }
}

Nel codice qui sopra, foo non può più essere soggetto ad inlining dal momento che necessita di conoscere sia se stesso che il suo chiamante. Questo non solo annulla possibili guadagni prestazionali ottenibili con l'inlining, ma spezza anche il principio di incapsulazione perché la funzione ora potrebbe essere dipendente da uno specifico contesto di esecuzione.

L'utilizzo di arguments.callee o di qualsiasi altra delle sue proprietà è altamente sconsigliato.

Costruttori

I costruttori in JavaScript sono differenti da quelli di molti altri linguaggi. Qualsiasi chiamata a funzione preceduta dalla parola chiave new agisce come un costruttore.

Dentro al costruttore (la funzione chiamata) il valore di this fa riferimento al nuovo oggetto creato. Il prototype di questo nuovo oggetto viene impostato al prototype dell'oggetto funzione che è stato invocato come costruttore.

Se la funzione che è stata chiamata non ha un'istruzione return esplicita, allora essa ritorna implicitamente il valore di this (il nuovo oggetto).

function Person(name) {
    this.name = name;
}

Person.prototype.logName = function() {
    console.log(this.name);
};

var sean = new Person();

Questo esempio chiama Person come costruttore ed imposta il prototype del nuovo oggetto creato a Person.prototype.

In caso di istruzione return esplicita, la funzione ritorna il valore specificato da quell'istruzione, ma solo se il valore di ritorno è un Object.

function Car() {
    return 'ford';
}
new Car(); // un nuovo oggetto, non 'ford'

function Person() {
    this.someValue = 2;

    return {
        name: 'Charles'
    };
}
new Person(); // l'oggetto ritornato ({name: 'Charles'}), escluso someValue

Quando la parola chiave new viene omessa, la funzione non ritornerà un nuovo oggetto.

function Pirate() {
    this.hasEyePatch = true; // imposta la proprietà nell'oggetto globale!
}
var somePirate = Pirate(); // somePirate è undefined

Mentre l'esempio precedente potrebbe sembrare essere funzionante in alcuni casi, a causa del modo in cui lavora this in JavaScript, esso userà l'oggetto globale come valore di this.

Factory (Fabbriche di oggetti)

Per poter omettere la parola chiave new, la funzione costruttore deve esplicitamente ritornare un valore.

function Robot() {
    var color = 'gray';
    return {
        getColor: function() {
            return color;
        }
    }
}
Robot.prototype = {
    someFunction: function() {}
};

new Robot();
Robot();

Entrambe le chiamate a Robot ritornano lo stesso risultato, un nuovo oggetto creato con una proprietà chiamata method, che è una Closure.

Bisogna anche notare che la chiamata new Robot() non influisce sul prototipo dell'oggetto ritornato. Mentre il prototipo sarà impostato con il nuovo oggetto creato, Robot non ritornerà mai quel nuovo oggetto.

Nell'esempio sopra, non c'è differenza funzionale nell'usare o meno la parola chiave new.

Creare nuovi oggetti tramite factory

Viene spesso raccomandato di non usare new perché una sua dimenticanza può portare a bug potenzialmente insidiosi da risolvere.

Per poter creare un nuovo oggetto, si dovrebbe invece usare una factory e costruire un nuovo oggetto all'interno di quella factory.

function CarFactory() {
    var car = {};
    car.owner = 'nobody';

    var milesPerGallon = 2;

    car.setOwner = function(newOwner) {
        this.owner = newOwner;
    }

    car.getMPG = function() {
        return milesPerGallon;
    }
    return car;
}

Sebbene questo esempio sia a prova di omissione della parola chiave new e renda sicuramente più semplice l'utilizzo delle variabili private, esso ha alcuni aspetti negativi.

  1. Usa più memoria dal momento che gli oggetti creati non condividono i metodi di un prototipo.
  2. Per poter ereditare, la factory deve copiare tutti i metodi da un altro oggetto oppure mettere quell'oggetto nel prototipo del nuovo oggetto.
  3. Perdere la catena di prototipi solo perché si vuole tralasciare la parola chiave new è contrario allo spirito del linguaggio.

In conclusione

Sebbene l'omissione della parola chiave new possa portare all'introduzione di bug, non è certo un motivo per privarsi completamente dell'uso dei prototipi. Alla fine si tratta di decidere quale sia la soluzione più adatta per l'applicazione. È specialmente importante scegliere uno specifico stile di creazione degli oggetti ed usarlo in maniera consistente.

Scope e spazi di nome (namespace)

Sebbene JavaScript non abbia problemi con la sintassi delle parentesi graffe per la definizione di blocchi, esso non supporta lo scope per blocco, quindi, tutto ciò che il linguaggio ci mette a disposizione è lo scope di funzione.

function test() { // questo è uno scope
    for(var i = 0; i < 10; i++) { // questo non è uno scope
        // conta
    }
    console.log(i); // 10
}

Anche gli spazi di nome (namespace) non sono gestiti in JavaScript, e ciò significa che ogni cosa viene definita in un namespace globalmente condiviso.

Ogni volta che ci si riferisce ad una variabile, JavaScript risale attraverso tutti gli scope fino a che non la trova e, nel caso esso raggiunga lo scope globale senza aver trovato il nome richiesto, solleva un ReferenceError.

Il problema delle variabili globali

// script A
foo = '42';

// script B
var foo = '42'

Questi due script non hanno lo stesso effetto. Lo script A definisce una variabile chiamata foo nello scope globale, mentre lo script B definisce una foo nello scope attuale.

Ancora una volta. Questo esempio non sortisce lo stesso effetto: il non utilizzo di var può avere importanti conseguenze.

// scope globale
var foo = 42;
function test() {
    // scope locale
    foo = 21;
}
test();
foo; // 21

L'omissione dell'istruzione var all'interno della funzione test sostituirà il valore di foo. Sebbene questo possa non sembrare un grosso problema in un primo momento, ritrovarsi con migliaia di linee di JavaScript senza utilizzare var introdurrà orribili bug molto difficili da individuare.

// scope globale
var items = [/* un elenco */];
for(var i = 0; i < 10; i++) {
    subLoop();
}

function subLoop() {
    // scope di subLoop
    for(i = 0; i < 10; i++) { // istruzione var omessa
        // fai qualcosa di eccezionale!
    }
}

Il ciclo esterno terminerà dopo la prima chiamata a subLoop, dato che subLoop sovrascriverà il valore globale di i. L'utilizzo di una var per il secondo ciclo for avrebbe facilmente evitato questo errore. L'istruzione var non dovrebbe mai essere omessa a meno che l'effetto desiderato non sia proprio quello di influenzare lo scope esterno.

Variabili locali

In JavaScript le sole sorgenti per le variabili locali sono i parametri funzione e le variabili dichiarate tramite l'istruzione var.

// scope globale
var foo = 1;
var bar = 2;
var i = 2;

function test(i) {
    // scope locale della funzione test
    i = 5;

    var foo = 3;
    bar = 4;
}
test(10);

Mentre foo e i sono variabili locali all'interno dello scope della funzione test, l'assegnazione di bar sostituirà la variabile globale con lo stesso nome.

Elevamento (hoisting)

JavaScript eleva le dichiarazioni. Questo significa che le istruzioni var e le dichiarazioni function verranno spostate in cima agli scope che le racchiudono.

bar();
var bar = function() {};
var someValue = 42;

test();
function test(data) {
    if (false) {
        goo = 1;

    } else {
        var goo = 2;
    }
    for(var i = 0; i < 100; i++) {
        var e = data[i];
    }
}

Il codice qui sopra, viene trasformato prima che inizi l'esecuzione. JavaScript sposta sia le istruzioni var che le dichiarazioni function in cima al più vicino scope che le racchiude.

// le istruzioni var vengono spostate qui
var bar, someValue; // di default a 'undefined'

// la dichiarazione function viene spostata qui
function test(data) {
    var goo, i, e; // il blocco scope mancante sposta qui queste istruzioni
    if (false) {
        goo = 1;
    } else {
        goo = 2;
    }
    for(i = 0; i < 100; i++) {
        e = data[i];
    }
}

bar(); // fallisce con un TypeError dato che bar è ancora 'undefined'
someValue = 42; // le assegnazioni non vengono influenzate dall'elevazione
bar = function() {};

test();

L'omissione del blocco di scope non solo muoverà le istruzioni var fuori dal corpo dei cicli, ma renderà anche i risultati di certi costrutti if poco intuitivi.

Nel codice originale, sebbene l'istruzione if sembrasse modificare la variabile globale goo, effettivamente essa va a modificare la variabile locale (dopo che l'elevazione è stata eseguita).

Senza la conoscenza dell'elevazione, uno potrebbe pensare che il codice qui sotto sollevi un ReferenceError.

// verifica se SomeImportantThing è stato inizializzato
if (!SomeImportantThing) {
    var SomeImportantThing = {};
}

Ma ovviamente tutto funziona grazie al fatto che l'istruzione var è stata spostata all'inzio dello scope globale.

var SomeImportantThing;

// qui altro codice potrebbe o meno inizializzare SomeImportantThing

// ci assicuriamo che ci sia
if (!SomeImportantThing) {
    SomeImportantThing = {};
}

Ordine di risoluzione dei nomi

Tutti gli scope in JavaScript, scope globale incluso, hanno lo speciale nome this definito in essi, che fa riferimento all'oggetto attuale.

Gli scope di funzione hanno anche il nome arguments definito in essi, che contiene gli argomenti passati alla funzione.

Per esempio, cercando di accedere ad una variabile di nome foo all'interno dello scope di una funzione, JavaScript effettuerà una ricerca del nome nel seguente ordine:

  1. Nel caso ci sia un'istruzione var foo nello scope attuale, usa quella.
  2. Se uno dei parametri funzione si chiama foo, usa quello.
  3. Se la funzione stessa si chiama foo, usa quella.
  4. Vai al successivo scope esterno e ricomincia dal numero 1.

Spazi di nome (Namespace)

Un comune problema associato al fatto di avere un solo spazio nomi globale, è che facilmente si incappa in problemi dove i nomi di variabile si sovrappongono. In JavaScript queso problema può essere facilmente evitato con l'aiuto dei contenitori anonimi.

(function() {
    // "namespace" auto contenuto

    window.foo = function() {
        // una closure esposta
    };

})(); // esecue immediatamente la funzione

Le funzioni anonime sono considerate espressioni, quindi per poter essere richiamabili, esse devono prima essere valutate.

( // valuta la funzione dentro le parentesi
function() {}
) // e ritorna l'oggetto funzione
() // richiama il risultato della valutazione

Ci sono altri modi per valutare e chiamare direttamente l'espressione funzione i quali, sebbene differenti nella sintassi, hanno tutti il medesimo effetto.

// Alcuni modi per invocare direttamente la
!function(){}()
+function(){}()
(function(){}());
// e così via...

In conclusione

Si raccomanda sempre di usare un contenitore anonimo per incapsulare il codice nel suo proprio namespace. Questo non solo protegge il codice da eventuali conflitti con i nomi, ma permette anche una migliore modularizzazione dei programmi.

Inoltre, l'uso delle variabili globali è considerato una cattiva pratica. Qualsiasi loro uso indica codice scritto male che è suscettibile ad errori e difficile da mantenere.

Array

Iterazione e proprietà degli Array

Sebbene gli array in JavaScript siano oggetti, non ci sono valide ragioni per usare il ciclo for in. Infatti, ci sono varie buone ragioni per evitare l'utilizzo di for in con gli array.

Dato che il ciclo for in enumera tutte le proprietà che sono presenti nella catena di prototipi, e dal momento che il solo modo per escludere queste proprietà è quello di usare hasOwnProperty, esso è già venti volte più lento di un normale ciclo for.

Iterazione

Per poter ottenere la miglior performance durante l'iterazione degli array, è meglio usare il classico ciclo for.

var list = [1, 2, 3, 4, 5, ...... 100000000];
for(var i = 0, l = list.length; i < l; i++) {
    console.log(list[i]);
}

In questo esempio c'è un ulteriore particolare da notare, che è il caching della lunghezza dell'array tramite l = list.length.

Sebbene la proprietà length sia definita nell'array stesso, c'è ancora un sovraccarico di lavoro dato dal fatto che deve essere ricercata ad ogni iterazione del ciclo. E mentre i motori JavaScript recenti potrebbero applicare delle ottimizzazioni in questo caso, non c'è modo di dire se il codice verrà eseguito su uno di questi nuovi motori oppure no.

Infatti, l'omissione della parte di caching può risultare in un ciclo eseguito soltanto alla metà della velocità con cui potrebbe essere eseguito facendo il caching della lunghezza.

La proprietà length

Mentre il getter della proprietà length ritorna semplicemente il numero di elementi che sono contenuti nell'array, il setter può essere usato per troncare l'array.

var arr = [1, 2, 3, 4, 5, 6];
arr.length = 3;
arr; // [1, 2, 3]

arr.length = 6;
arr.push(4);
arr; // [1, 2, 3, undefined, undefined, undefined, 4]

Assegnando una lunghezza più piccola si tronca l'array. Incrementandola si crea un array frammentato.

In conclusione

Per la miglior performance, si raccomanda di usare sempre il ciclo for classico e fare il caching della proprietà length. L'uso di for in su di un array è segno di un codice scritto male che è suscettibile a bug e pessima performance.

Il costruttore Array

Dato che il costruttore Array è ambiguo riguardo a come esso gestisca i suoi parametri, si consiglia calorosamente di usare l'array letterale (notazione []) quando si creano array.

[1, 2, 3]; // Risultato: [1, 2, 3]
new Array(1, 2, 3); // Risultato: [1, 2, 3]

[3]; // Risultato: [3]
new Array(3); // Risultato: []
new Array('3') // Risultato: ['3']

Nei casi in cui c'è solo un argomento passato al costruttore Array e quando l'argomento è un Number, il costruttore ritornerà un nuovo array frammentato con la proprietà length impostata al valore dell'argomento. Si noti che in questo modo solo la proprietà length del nuovo array verrà impostata, mentre gli indici dell'array non verranno inizializzati.

var arr = new Array(3);
arr[1]; // undefined
1 in arr; // false, l'indice non è stato impostato

Essere in grado di impostare la lunghezza dell'array in anticipo è utile soltanto in poche situazioni, come ad esempio la ripetizione di una stringa, nel cui caso si eviterebbe l'uso di un ciclo.

new Array(count + 1).join(stringToRepeat);

In conclusione

I letterali sono da preferirsi al costruttore Array. Sono più concisi, hanno una sintassi più chiara ed incrementano la leggibilità del codice.

Tipi di dati

Uguaglianza e comparazioni

JavaScript usa due differenti metodi per comparare l'uguaglianza dei valori degli oggetti.

L'operatore di uguaglianza

L'operatore di uguaglianza consiste di due segni di uguaglianza: ==.

JavaScript supporta la tipizzazione debole. Questo significa che l'operatore di uguaglianza converte i tipi in modo da poterli confrontare.

""           ==   "0"           // false
0            ==   ""            // true
0            ==   "0"           // true
false        ==   "false"       // false
false        ==   "0"           // true
false        ==   undefined     // false
false        ==   null          // false
null         ==   undefined     // true
" \t\r\n"    ==   0             // true

Questa tabella mostra i risultati della conversione di tipo, ed è il principale motivo per cui l'uso di == è ampiamente considerato una cattiva pratica. Esso introduce bug difficili da rilevare a causa delle complesse regole di conversione.

Inoltre, c'è anche un impatto sulla performance quando entra in gioco la conversione di tipo. Ad esempio, una stringa deve essere convertita in un numero prima di poter essere confrontata con un altro numero.

L'operatore di uguaglianza stretta

L'operatore di uguaglianza stretta consiste di tre segni di uguaglianza: ===.

Funziona come il normale operatore di uguaglianza, con l'eccezione di non eseguire la conversione di tipo tra gli operandi.

""           ===   "0"           // false
0            ===   ""            // false
0            ===   "0"           // false
false        ===   "false"       // false
false        ===   "0"           // false
false        ===   undefined     // false
false        ===   null          // false
null         ===   undefined     // false
" \t\r\n"    ===   0             // false

I risultati qui sono più chiari e permettono di identificare subito un problema con il codice. Questo rende il codice più solido di un certo grado e fornisce anche migliorie alla performance nel caso di operandi di tipo differente.

Comparazione di oggetti

Nonostante == e === vengano definiti operatori di uguaglianza, essi funzionano differentemente quando almeno uno degli operandi è un Object.

{} === {};                   // false
new String('foo') === 'foo'; // false
new Number(10) === 10;       // false
var foo = {};
foo === foo;                 // true

Qui, entrambe gli operatori confrontano per identità e non per uguaglianza. Essi confrontano, cioè, che sia la stessa istanza dell'oggetto, in modo molto simile a is in Python e la comparazione di puntatori in C.

In conclusione

Si raccomanda calorosamente di usare solo l'operatore di uguaglianza stretta. Nei casi dove è necessario che i tipi vengano convertiti, questa operazione dovrebbe essere fatta esplicitamente piuttosto che essere lasciata alle complesse regole di conversione del linguaggio.

L'operatore typeof

L'operatore typeof (assieme a instanceof) è probabilmente il più grande difetto di progettazione di JavaScript, dato che è quasi completamente inusabile.

Sebbene instanceof abbia ancora limitati casi d'uso, typeof ha realmente un solo caso d'uso, che non è quello di verificare il tipo di un oggetto.

Tabella dei tipi di JavaScript

Valore              Classe     Tipo
-------------------------------------
"foo"               String     string
new String("foo")   String     object
1.2                 Number     number
new Number(1.2)     Number     object
true                Boolean    boolean
new Boolean(true)   Boolean    object
new Date()          Date       object
new Error()         Error      object
[1,2,3]             Array      object
new Array(1, 2, 3)  Array      object
new Function("")    Function   function
/abc/g              RegExp     object (function in Nitro/V8)
new RegExp("meow")  RegExp     object (function in Nitro/V8)
{}                  Object     object
new Object()        Object     object

In questa tabella, Tipo fa riferimento al valore ritornato dall'operatore typeof. Come si può chiaramente vedere, questo valore è tutto fuorchè affidabile.

Classe si riferisce al valore della proprietà interna [[Class]] di un oggetto.

Per ottenere il valore di [[Class]], bisogna usare il metodo toString di Object.prototype.

La classe di un oggetto

Le specifiche forniscono esattamente un modo per accedere al valore di [[Class]], con l'uso di Object.prototype.toString.

function is(type, obj) {
    var clas = Object.prototype.toString.call(obj).slice(8, -1);
    return obj !== undefined && obj !== null && clas === type;
}

is('String', 'test'); // true
is('String', new String('test')); // true

Nel esempio qui sopra, Object.prototype.toString viene chiamato con il valore di this impostato all'oggetto di cui si vuole ottenere il valore di [[Class]].

Testare variabili non definite

typeof foo !== 'undefined'

Questo esempio verificherà se foo è stata attualmente dichiarata oppure no. Un semplice referenziamento ad essa risulterebbe in un ReferenceError. Questo è l'unico caso in cui typeof è utile a qualcosa.

In conclusione

Per verificare il tipo di un oggetto, è altamente raccomandato l'utilizzo di Object.prototype.toString, dato che questo è il solo modo affidabile per fare ciò. Come mostrato nella tabella precedente, alcuni valori di ritorno di typeof non sono definiti nelle specifiche, e ciò dimostra come essi potrebbero differire tra implementazioni differenti.

A meno che non si debba verificare se una variabile è definta, typeof dovrebbe essere evitato.

L'operatore instanceof

L'operatore instanceof confronta i costruttori dei suoi due operandi. È utile soltanto per la comparazione di oggetti realizzati dal programmatore. Se usato sui tipi interni del linguaggio, esso è praticamente inutile alla stregua dell'operatore typeof.

Confronto di oggetti personalizzati

function Foo() {}
function Bar() {}
Bar.prototype = new Foo();

new Bar() instanceof Bar; // true
new Bar() instanceof Foo; // true

// Questo imposta Bar.prototype all'oggetto funzione Foo,
// ma non ad un'istanza di Foo
Bar.prototype = Foo;
new Bar() instanceof Foo; // false

Uso di instanceof con i tipi nativi

new String('foo') instanceof String; // true
new String('foo') instanceof Object; // true

'foo' instanceof String; // false
'foo' instanceof Object; // false

Un'importante cosa da notare qui è che instanceof non funziona con oggetti originati da differenti contesti JavaScript (ad esempio, differenti documenti in un browser web), dato che i loro costruttori non saranno esattamente lo stesso oggetto.

In conclusione

L'operatore instanceof dovrebbe essere usato solo quando si ha a che fare con oggetti personalizzati creati dal programmatore, che provengono dallo stesso contesto JavaScript. Proprio come per l'operatore typeof, ogni altro tipo di utilizzo dovrebbe essere evitato.

Conversione di tipo (Type Casting)

JavaScript è un linguaggio debolmente tipizzato, perciò esso applicherà una conversione di tipo ovunque sia possibile.

// Queste sono vere
new Number(10) == 10; // l'oggetto Number viene convertito
                      // in una primitiva numero tramite chiamata implicita
                      // al metodo Number.prototype.valueOf

10 == '10';           // String viene convertita in Number
10 == '+10 ';         // Stringa più assurda
10 == '010';          // a ancora di più
isNaN(null) == false; // null viene convertito in 0
                      // che ovviamente non è NaN

// Queste sono false
10 == 010;
10 == '-10';

Per evitare i problemi appena visti, l'uso dell'operatore di uguaglianza stretta è altamente raccomandato. Sebbene questo eviti molti dei comuni problemi, ci sono ancora molti ulteriori problemi che possono essere generati dal sistema debolmente tipizzato di JavaScript.

Costruttori di tipi interni

I costruttori dei tipi interni del linguaggio, come Number e String, funzionano in modo differente a seconda che venga usata o meno la parola chiave new.

new Number(10) === 10;     // False, Object e Number
Number(10) === 10;         // True, Number e Number
new Number(10) + 0 === 10; // True, a causa della conversione implicita

L'uso di un tipo di dato interno come Number come costruttore, creerà un nuovo oggetto Number, ma l'omissione della parola chiave new farà sì che la funzione Number agisca da convertitore.

Inoltre, il passaggio di valori letterali o non oggetto risulterà in un'ancora maggiore conversione di tipo.

La miglior opzione è quella di fare esplicitamente la conversione ad uno dei tre possibili tipi.

Convertire in una stringa

'' + 10 === '10'; // true

Anteponendo una stringa vuota, un valore può facilmente essere convertito in una stringa.

Convertire in un numero

+'10' === 10; // true

Usando l'operatore unario di addizione, è possibile convertire in un numero.

Convertire in un booleano

Usando due volte l'operatore not, un valore può essere convertito in un booleano.

!!'foo';   // true
!!'';      // false
!!'0';     // true
!!'1';     // true
!!'-1'     // true
!!{};      // true
!!true;    // true

Base

Perché non usare eval

La funzione eval eseguirà una stringa di codice JavaScript nello scope locale.

var number = 1;
function test() {
    var number = 2;
    eval('number = 3');
    return number;
}
test(); // 3
number; // 1

Comunque, eval esegue solo nello scope locale quando viene chiamata direttamente e quando il nome della funzione chiamata è eval.

var number = 1;
function test() {
    var number = 2;
    var copyOfEval = eval;
    copyOfEval('number = 3');
    return number;
}
test(); // 2
number; // 3

L'uso di eval dovrebbe essere evitato. Il 99.9% dei suoi "utilizzi" può essere ottenuto senza di essa.

eval sotto mentite spoglie

Le funzioni di timeout setTimeout e setInterval possono entrambe accettare una stringa come loro primo argomento. Questa stringa verrà sempre eseguita nello scope globale dato che eval non viene chiamato direttamente in questo caso.

Problemi di sicurezza

eval è anche un problema di sicurezza, perché essa esegue qualsiasi codice le viene passato. Non si dovrebbe mai usare con stringhe di origine sconosciuta o inaffidabile.

In conclusione

eval non dovrebbe mai essere usata. Qualsiasi codice che ne faccia uso dovrebbe essere messo in discussione sotto l'aspetto della funzionalità, della performance e della sicurezza. Se qualcosa richiede eval per poter funzionare, allora non dovrebbe essere usato in primo luogo, ma si dovrebbe prevedere una miglior progettazione che non richieda l'uso di eval.

undefined e null

JavaScript usa due valori distinti per il nulla, null e undefined, e quest'ultimo è il più utile.

Il valore undefined

undefined è un tipo con esattamente un valore: undefined.

Il linguaggio definisce anche una variabile globale che ha il valore di undefined. Questa variabile è anche chiamata undefined. Comunque, questa variabile non è né una costante né una parola chiave del linguaggio. Ciò significa che il suo valore può facilmente essere sovrascritto.

Ecco alcuni esempi di quando il valore undefined viene ritornato:

  • Accedendo la variabile globale (non modificata) undefined.
  • Accedendo una variabile dichiarata ma non ancora inizializzata.
  • Ritorno implicito da funzioni che non hanno l'istruzione return.
  • Istruzioni return che non ritornano esplicitamente alcun valore.
  • Ricerca di proprietà inesistenti.
  • Parametri funzione a cui non viene esplicitamente passato alcun valore.
  • Qualsiasi cosa a cui sia stato assegnato il valore undefined.
  • Qualsiasi espressione nella forma di void(espressione).

Gestire le modifiche al valore di undefined

Dato che la variabile globale undefined mantiene solo una copia dell'attuale valore di undefined, assegnandole un nuovo valore non cambia il valore del tipo undefined.

Inoltre, per confrontare qualcosa con il valore di undefined, è necessario ottenere prima il valore di undefined.

Per proteggere il codice da possibili sovrascritture della variabile undefined, viene usata una comune tecnica che prevede l'aggiunta di un ulteriore parametro ad un contenitore anonimo al quale non viene passato alcun argomento.

var undefined = 123;
(function(something, foo, undefined) {
    // ora undefined nello scope locale
    // fa nuovamente riferimento al valore `undefined`

})('Hello World', 42);

Un altro modo per ottenere lo stesso effetto sarebbe quello di usare una dichiarazione all'interno del contenitore.

var undefined = 123;
(function(something, foo) {
    var undefined;
    ...

})('Hello World', 42);

La sola differenza è che questa versione si traduce in 4 byte in più quando minificata, e non c'è nessun'altra istruzione var al'interno del contenitore anonimo.

Utilizzi di null

Mentre undefined nel contesto del linguaggio JavaScript viene principalmente usato come un tradizionale null, l'attuale null (sia letterale che tipo di dati) è più o meno solo un altro tipo di dato.

Viene usato in alcune funzioni interne al JavaScript (come la dichiarazione del termine della catena di prototipi, impostando Foo.prototype = null), ma nella maggior parte dei casi, può essere rimpiazzato da undefined.

Inserimento automatico dei punti-e-virgola

Sebbene JavaScript utilizzi lo stile di sintassi del C, esso non obbliga l'uso dei punti-e-virgola nel codice sorgente, perciò è possibile ometterli.

Detto questo, JavaScript non è un linguaggio che fa a meno dei punti-e-virgola. Infatti, esso necessita di punti-e-virgola per poter comprendere il codice sorgente. Quindi, il parser del JavaScript li inserisce automaticamente ogni volta che incontra un errore di analisi dato dalla mancanza di un punto-e-virgola.

var foo = function() {
} // errore di analisi, atteso punto-e-virgola
test()

Quindi avviene l'inserimento, ed il parser prova nuovamente.

var foo = function() {
}; // nessun errore, il parser continua
test()

L'inserimento automatico dei punti-e-virgola è considerato essere uno dei più grandi errori di progettazione del linguaggio, perché può modificare il comportamento del codice.

Come funziona

Il codice qui sotto non ha punti-e-virgola, quindi sta al parser decidere dove inserirli.

(function(window, undefined) {
    function test(options) {
        log('testing!')

        (options.list || []).forEach(function(i) {

        })

        options.value.test(
            'long string to pass here',
            'and another long string to pass'
        )

        return
        {
            foo: function() {}
        }
    }
    window.test = test

})(window)

(function(window) {
    window.someLibrary = {}

})(window)

Di seguito il risultato del gioco da "indovino" del parser.

(function(window, undefined) {
    function test(options) {

        // Non inserito, linee unite
        log('testing!')(options.list || []).forEach(function(i) {

        }); // <- inserito

        options.value.test(
            'long string to pass here',
            'and another long string to pass'
        ); // <- inserito

        return; // <- inserito, invalida l'istruzione return
        { // trattato come un blocco

            // un'etichetta e una singola espressione
            foo: function() {}
        }; // <- inserito
    }
    window.test = test; // <- inserito

// Le linee vengono unite nuovamente
})(window)(function(window) {
    window.someLibrary = {}; // <- inserito

})(window); //<- inserito

Il parser ha drasticamente modificato il comportamento del codice. In alcuni casi, questo porta ad eseguire cose sbagliate.

Parentesi ad inizio riga

Nel caso di parentesi ad inizio riga, il parser non inserirà un punto-e-virgola.

log('testing!')
(options.list || []).forEach(function(i) {})

Questo codice viene trasformato in una sola linea.

log('testing!')(options.list || []).forEach(function(i) {})

Le possibilità che log non ritorni una funzione sono veramente alte, perciò il codice qui sopra porterà ad un TypeError dichiarando che undefined is not a function (undefined non è una funzione).

In conclusione

È fortemente raccomandato di non omettere mai i punti-e-virgola. Si raccomanda anche di mantenere le parentesi sulla stessa linea della corrispondente istruzione, e di non ometterle mai in istruzioni if / else a linea singola. Queste misure precauzionali non solo miglioreranno la consistenza del codice, ma preverranno anche che il parser JavaScript modifichi il comportamento del codice in modo inaspettato.

L'operatore delete

In breve, è impossibile eliminare variabili globali, funzioni e qualche altra cosa in JavaScript che ha l'attributo DontDelete impostato.

Codice globale e codice funzione

Quando una variabile o una funzione viene definita in un scope globale o funzione, essa è una proprietà dell'oggetto Activation o dell'oggetto Global. Queste proprietà hanno un set di attributi, tra i quali DontDelete. Dichiarazioni di variabile o funzione nel codice globale o funzione, creano sempre proprietà con DontDelete, e quindi non possono essere eliminate.

// variabile globale:
var a = 1; // DontDelete è impostato
delete a; // false
a; // 1

// funzione normale:
function f() {} // DontDelete è impostato
delete f; // false
typeof f; // "function"

// la riassegnazione non aiuta:
f = 1;
delete f; // false
f; // 1

Proprietà esplicite

Proprietà esplicitamente impostate possono essere eliminate normalmente.

// proprietà impostata esplicitamente:
var obj = {x: 1};
obj.y = 2;
delete obj.x; // true
delete obj.y; // true
obj.x; // undefined
obj.y; // undefined

Nel codice qui sopra, obj.x e obj.y possono essere eliminate perché non hanno l'attributo DontDelete. Ecco perché anche l'esempio seguente funziona.

// questo funziona, tranne che per IE:
var GLOBAL_OBJECT = this;
GLOBAL_OBJECT.a = 1;
a === GLOBAL_OBJECT.a; // true - solo una variabile globale
delete GLOBAL_OBJECT.a; // true
GLOBAL_OBJECT.a; // undefined

Qui usiamo un trucco per eliminare a. this qui fa riferimento all'oggetto Global e noi dichiariamo esplicitamente la variabile a come sua proprietà, il che ci permette di eliminarla.

IE (almeno 6-8) ha alcuni bug, quindi il codice precedente non funziona.

Argomenti funzione e proprietà interne

Anche i normali argomenti delle funzioni, gli oggetti arguments e le proprietà interne hanno DontDelete impostato.

// argomenti funzione e proprietà:
(function (x) {

  delete arguments; // false
  typeof arguments; // "object"

  delete x; // false
  x; // 1

  function f(){}
  delete f.length; // false
  typeof f.length; // "number"

})(1);

Oggetti non nativi (host)

Il comportamento dell'operatore delete può essere inaspettato con gli oggetti non nativi. A causa delle specifiche, agli oggetti non nativi è permesso di implementare qualsiasi tipo di funzionalità.

In conclusione

L'operatore delete spesso ha un comportamento inaspettato e può solo essere usato con sicurezza per eliminare proprietà esplicitamente impostate in oggetti normali.

Varie

setTimeout e setInterval

Dato che JavaScript è asincrono, è possibile programmare l'esecuzione di una funzione usando le funzioni setTimeout e setInterval.

function foo() {}
var id = setTimeout(foo, 1000); // ritorna un Number > 0

Quando chiamato, setTimeout ritorna l'ID del timeout e programma foo per essere eseguito approssimativamente un migliaio di millisecondi nel futuro. foo verrà quindi eseguito una volta.

Dipendendo dalla risoluzione del timer del motore JavaScript che esegue il codice, come anche dal fatto che JavaScript è single threaded e quindi altro codice potrebbe essere eseguito bloccando il thread, non è mai sicuro scommettere che una funzione verrà eseguita esattamente al ritardo specifiato nella chiamata a setTimeout.

La funzione che è stata passata come primo parametro verrà chiamata dall'oggetto globale, e ciò significa che this all'interno della funzione chiamata farà riferimento all'oggetto globale.

function Foo() {
    this.value = 42;
    this.method = function() {
        // this fa riferimento all'oggetto globale
        console.log(this.value); // stamperà undefined
    };
    setTimeout(this.method, 500);
}
new Foo();

Sovrapposizione di chiamate con setInterval

Mentre setTimeout esegue solo una volta la funzione, setInterval (come il nome suggerisce) eseguirà la funzione ogni X millisecondi, ma il suo utilizzo è sconsigliato.

Quando il codice che viene eseguito blocca la chiamata timeout, setInterval eseguirà ancora più chiamate alla specifica funzione. Questo può, specialmente con intervalli molto brevi, tradursi in chiamate a funzione che si sovrappongono.

function foo(){
    // qualcosa che blocca per 1 secondo
}
setInterval(foo, 1000);

Nel codice precedente, foo verrà chiamato una volta e quindi bloccherà per un secondo.

Mentre foo blocca il codice, setInterval continuerà a programmare ulteriori chiamate ad essa. Ora, quando foo ha finito, ci saranno già dieci ulteriori chiamate ad essa in attesa per essere eseguite.

Gestione di potenziale codice bloccante

La soluzione più semplice, come anche la più controllabile, è quella di usare setTimeout all'interno di se stessa.

function foo(){
    // qualcosa che blocca per 1 secondo
    setTimeout(foo, 1000);
}
foo();

Non solo questo incapsula la chiamata a setTimeout, ma previene anche la sovrapposizione delle chiamate e da un controllo addizionale. foo stessa può ora decidere se vuole continuare ad essere eseguita oppure no.

Pulizia manuale dei timeout

La pulizia di timeout ed intervalli funziona passando il rispettivo ID a clearTimeout o clearInterval, in base a quale set di funzioni è stato usato precedentemente.

var id = setTimeout(foo, 1000);
clearTimeout(id);

Pulizia di tutti i timeout

Dato che non c'è un metodo interno per la pulizia di tutti i timeout e/o intervalli, è necessario usare la forza bruta per poter raggiungere questo scopo.

// pulisce "tutti" i timeout
for(var i = 1; i < 1000; i++) {
    clearTimeout(i);
}

Ma ci potrebbero ancora essere timeout che non vengono toccati da questo numero arbitrario. Un altro modo per ottenere ciò, è considerare che l'ID dato ad un timeout viene incrementato di uno ogni volta che si chiama setTimeout.

// pulisce "tutti" i timeout
var biggestTimeoutId = window.setTimeout(function(){}, 1),
i;
for(i = 1; i <= biggestTimeoutId; i++) {
    clearTimeout(i);
}

Sebbene questo funzioni con la maggior parte dei browser odierni, non è specificato che gli ID debbano essere ordinati in quel modo e ciò potrebbe anche cambiare in futuro. Perciò, si raccomanda di tener traccia di tutti gli ID dei timeout, così che possano essere puliti in modo specifico.

Uso nascosto di eval

setTimeout e setInterval possono anche accettare una stringa come loro primo parametro. Questa caratteristica non dovrebbe essere mai usata perché internamente fa uso di eval.

function foo() {
    // verrà chiamata
}

function bar() {
    function foo() {
        // non verrà mai chiamata
    }
    setTimeout('foo()', 1000);
}
bar();

Dal momento che eval non viene chiamata direttamente in questo caso, la stringa passata a setTimeout verrà eseguita nello scope globale. Quindi, non verrà usata la variabile locale foo dallo scope di bar.

Si raccomanda inoltre di non usare una stringa per passare argomenti alla funzione che verrà chiamata da una delle funzioni di timeout.

function foo(a, b, c) {}

// non usare MAI questo
setTimeout('foo(1, 2, 3)', 1000)

// Usare invece una funzione anonima
setTimeout(function() {
    foo(1, 2, 3);
}, 1000)

In conclusione

Una stringa non dovrebbe mai essere usata come parametro di setTimeout o setInterval. È un chiaro segno di codice veramente pessimo, quando gli argomenti necessitano di essere passati alla funzione che deve essere chiamata. Dovrebbe invece essere passata una funzione anonima che si incarichi di gestire l'effettiva chiamata.

Inoltre, l'uso di setInterval dovrebbe essere evitato perché il suo schedulatore non viene bloccato dall'esecuzione di JavaScript.