Introdução

Introdução

JavaScript Garden é uma coletânea crescente que documenta as peculiaridades da linguagem de programação JavaScript. Nela você encontra recomendações para escapar dos erros comuns aos mais sutís, bem como de problemas de desempenho e práticas ruins, que programadores novatos podem acabar encontrando enquanto se aprofundam na linguagem.

JavaScript Garden não tem como propósito te ensinar à programar em JavaScript. Conhecimento prévio da linguagem é fortemente recomendado para que você entenda o conteúdo dos tópicos abordados neste guia. A fim de aprender as noções básicas da linguagem, por favor, consulte o excelente guia disponível na Mozilla Developer Network.

Os autores

Este guia é fruto do trabalho de dois excelentes usuários do Stack Overflow, Ivo Wetzel (Conteúdo) e Zhang Yi Jiang (Design).

É mantido atualmente por Tim Ruffles.

Contribuidores

Hospedagem

JavaScript Garden está hospedado no GitHub, porém Cramer Development nos apoia com um espelho em JavaScriptGarden.info.

Licença

JavaScript Garden está publicado sob a licença MIT e hospedado no GitHub. Caso você encontre defeitos ou erros de digitação, por favor registre o problema ou realize um pull request no repositório. Você também pode nos encontrar na sala de JavaScript no chat do Stack Overflow.

Objetos

Propriedades e manipulação de objetos

Tudo em JavaScript se comporta como um objeto, com apenas duas exceções que são null e undefined.

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

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

Um equívoco muito comum é a idéia de que números não podem ser manipulados como objetos. O parser do JavaScript analisa a notação de ponto como ponto flutuante de um número.

2.toString(); // raises SyntaxError

Existem três soluções para contornar este problema e permtir que números se comportem como objetos.

2..toString(); // o segundo ponto é reconhecido corretamente
2 .toString(); // perceba o espaço deixado à esquerda do ponto
(2).toString(); // 2 é interpretado primeiro

Objetos como tipo de dados

Em JavaScript, Objetos podem também ser utilizados como Hashmaps; eles consistem principalmente de propriedades nomeadas, que apontam para valores.

Usando um objeto literal - notação do tipo {}- é possível criar um objeto simples. Este novo objeto herda de Object.prototype e não possui propriedades próprias definidas.

var foo = {}; // um novo objeto vazio

// um novo objeto com uma propriedade 'test' populada com o valor 12
var bar = {test: 12}; 

Acessando propriedades

As propriedades de um objeto podem ser acessadas de duas maneiras, através da notação de ponto ou da notação de colchete.

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

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

foo.1234; // Erro de sintaxe
foo['1234']; // funciona

Ambas as notações trabalham de forma quase idêntica, com a única diferença de que o colchete permite configuração dinâmica de propriedades e uso de propriedades nomeadas que de outra maneira levaria à erros de sintaxe.

Removendo propriedades

A única maneira de remover uma propriedade de um objeto é através do operador delete; definir uma propriedade como undefined ou null somente apaga o valor associado com tal propriedade, mas não remove a key.

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]);
    }
}

O código acima retorna tanto bar undefined quantofoo null - somente baz foi removido e, portanto, não saiu no output.

Notações de Keys

var test = {
    'case': 'I am a keyword, so I must be notated as a string',
    delete: 'I am a keyword, so me too' // dispara SyntaxError
};

Propriedades de objetos podem ser tanto representadas como caracteres simples bem como strings. Devido a outro engano do parser do JavaScript, o exemplo acima irá retornar SyntaxError remetendo ao ECMAScript 5.

Este erro decorre do fato de que 'apagar' é uma palavra reservada; por consequencia, deve ser representada como uma string literal a fim de garantir a interpretação correta pelas antigas engines de JavaScript.

Prototype

JavaScript não dispõe de nenhum modelo clássico de herança; em vez disso, ele faz uso do modelo prototypal.

Enquanto isto é considerado muitas vezes como sendo um dos pontos fracos do JavaScript, o modelo de herança prototypal é de fato muito mais poderoso do que o modelo clássico. Por exemplo, isto torna relativamente trivial construir um modelo clássico com base no modelo prototypal, enquanto que o contrário se verifica como uma tarefa mais difícil.

JavaScript é a única linguagem amplamente utilizada que apresenta um modelo de herança do tipo prototypal, por isso pode levar algum tempo até que você se ajuste às diferenças entre os dois modelos.

A primeira grande diferença é que herança em JavaScript utiliza o conceito de cadeias prototype.

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

function Bar() {}

// Apontar Bar's prototype para uma nava instância de Foo
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';

// Tenha certeza de que Bar é o construtor atual
Bar.prototype.constructor = Bar;

var test = new Bar(); // criar uma nova instância de bar

// A cadeia prototype resultante
test [instance of Bar]
    Bar.prototype [instance of Foo]
        { foo: 'Hello World', value: 42 }
        Foo.prototype
            { method: ... }
            Object.prototype
                { toString: ... /* etc. */ }

No código acima, o objeto test irá herdar de ambos Bar.prototype e Foo.prototype; portanto, ele terá acesso à função method que foi definida em Foo. Ele também terá acesso à propriedade value da única instância de Foo que é seu próprio prototype. É importante perceber que new Bar() não cria uma nova instância de Foo, mas reutiliza aquela associada ao prototype; assim, todas as intâncias Bar dividirão a mesma propriedade value.

Buscando propriedades

Ao acessar as propriedades de um objeto, JavaScript irá percorre a cadeia prototype até o topo para encontrar a propriedade solicitada.

Caso atinja o topo da cadeia - denominada Object.prototype - e não encontre a propriedade especificada, o valor undefined será retornado.

A propriedade Prototype

Enquanto a propriedade prototype é utilizada pela linguagem na contrução de cadeia de prototype, ainda é possível associar qualquer valor dado a ele. No entanto, tipos primitivos serão ignorados quando associados como prototype.

function Foo() {}
Foo.prototype = 1; // sem efeito

Atribuindo objetos, como demonstrado no exemplo anterior, irá funcionar, e permite a criação dinâmica de cadeias prototype.

Performance

O tempo de pesquisa por propriedades que estão no topo da cadeia prototype pode ter um impacto negativo na performance, principalmente em código onde a performance é um fator crítico. Além disso, a busca por propriedades que não existem também atravessa a cadeia prototype.

Além disso, ao interagir com propriedades de um objeto cada propriedade na cadeia prototype será enumerada.

Estendendo Prototypes nativos

Uma prática ruim que é normalmente utilizada é a de estender Object.prototype ou qualquer outro prototype construído.

Esta técnica é denominada monkey patching e quebra o encapsulamento. Mesmo utilizada por frameworks populars como Prototype, não existe mais razão para poluir tipos built-in com funcionalidades adicionais fora de padrão.

A única boa razão existente para continuar estendendo um built-in prototype é a de assegurar as novas funcionalidade de engines JavaScript modernas; por exemplo, Array.forEach.

Conclusão

É essencial entender o modelo de herança prototypal antes de escrever código complexo que faço uso do mesmo. Além disso, tome cuidado com o tamanho da cadeia prototype em seu código e a refatore caso necessário a fim de evitar futuros problemas de performance. A respeito do prototypes nativos, estes nunca devem ser estendidos ao menos que seja para manter a compatibilidade com novas características do JavaScript.

hasOwnProperty

Para verificar se uma propriedade está definida no próprio objeto e não em outro lugar da sua cadeia prototype, é necessário utilizar o método hasOwnProperty o qual todos os objetos herdam de Object.prototype.

hasOwnProperty é a única coisa em JavaScript a qual lida com propriedades e não percorre a cadeia prototype.

// Poluindo 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

Somente hasOwnProperty irá retornar o resultado correto e esperado; isto é essencial quando se interage sobre propriedades de qualquer objeto. Não existe outra maneira de verificar propriedades que não estejam definidas no próprio objeto, mas em outro lugar na cadeia prototype.

hasOwnProperty como propriedade

JavaScript não protege o nome da propriedade hasOwnProperty; assim, se existe a possibilidade de algum objeto possuir uma propriedade com este mesmo nome, torna-se necessário utilizar um hasOwnProperty externo a fim de obter resultados corretos.

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

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

// Utiliza hasOwnProperty de outro objeto e o instancia com 'this' apontado para foo
({}).hasOwnProperty.call(foo, 'bar'); // true

// Também é possível utilizar hasOwnProperty do Object
// prototype para este fim
Object.prototype.hasOwnProperty.call(foo, 'bar'); // true

Conclusão

O método hasOwnProperty é a única maneira confiável para verificar a existência da propriedade em um objeto. É recomendado que hasOwnProperty seja utilizado em cada interação de um laço for in a fim de evitar erros de extensão do prototype.

O laço for in

Assim como o operador in, o laço for in percorre a cadeia prototype quando interage sobre as propriedades de um objeto.

// Poluindo o Object.prototype
Object.prototype.bar = 1;

var foo = {moo: 2};
for(var i in foo) {
    console.log(i); // retorna ambos bar e moo
}

Uma vez que não é possível alterar o comportamento do laço for in por si só, faz-se necessário filtrar as propriedades do objeto durante o ciclo de repetição do laço; isso é feito usando o método hasOwnProperty do Object.prototype.

Utilizando hasOwnProperty como filtro

// o mesmo foo utilizado anteriormente
for(var i in foo) {
    if (foo.hasOwnProperty(i)) {
        console.log(i);
    }
}

Esta é única forma correta de usar. Devido ao uso de hasOwnProperty, o exemplo irá retornar moo. Quando hasOwnProperty é deixado de lado, o código fica propenso a erros nos casos em que o prototype - por exemplo Object.prototype- tenha sido estendido.

Um framework largamente utilizado que estende o Object.prototype é Prototype. Quando este framework é utilizado, laços for in que não utilizam hasOwnProperty ficam desprotegidos contra erros.

Conclusão

Recomenda-se utilizar hasOwnProperty sempre. Nunca faça pressuposições sobre o ambiente em que o código está sendo executado, ou se os prototypes nativos foram estendidos ou não.

Funções

Declaração de funções e expressões

Funções em JavaScript são objetos de primeira classe. Isto significa que elas podem ser tratadas como qualquer outro tipo. Um uso muito comum desta característica é o de passar uma função anônima como uma callback para outra, talvez uma função assíncrona.

A declaração function

function foo() {}

A função acima sofrerá hoisting antes que a execução do programa se inicie; assim, ela estará disponível em todo o escopo em que foi definida, até mesmo se for chamada antes de ter sido definida no código.

foo(); // Funciona pois foo foi elevada para o topo do escopo
function foo() {}

A expressão function

var foo = function() {};

Este exemplo atribui uma função sem nome e anônima à variável foo.

foo; // 'undefined'
foo(); // dispara TypeError
var foo = function() {};

Devido ao fato de que var é uma declaração que eleva a definição da variável foo ao topo do escopo, esta sofrerá hoist e foo estará declarado logo que o script for executado.

Como atribuições só ocorrem em tempo de execução, o valor default de foo será undefined antes que o código seja executado.

Expressão de uma função nomeada

Outro caso especial é a atribuição de funções nomeadas.

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

Aqui, bar não está disponível fora do escopo, uma vez que a função se encontra atribuída em foo; no entanto, dentro de bar, ela esta disponível. Isto ocorre devido ao fato de como o name resolution funciona em JavaScript, o nome da função está sempre disponível no escopo local da própria função.

Como funciona o this

JavaScript tem uma concepção diferente sobre a que a palavra reservada this se refere da maioria das outras linguagens de programação. Existem exatamente cinco diferentes maneiras as quais os valores de this podem ser referenciados na linguagem.

O escopo Global

this;

Quando usando this no escopo global, ele simplesmente estará apontando para o objeto global.

Chamando uma função

foo();

Aqui, this irá referenciar novamente o objeto global.

Chamando um método

test.foo(); 

Neste exemplo, this irá referenciar test.

Chamando um construtor

new foo(); 

Uma chamada de função que é precedida pela palavra chave new age como um construtor. Dentro da função, this irá se referir a um objeto recém criado.

Referência explícita do this

function foo(a, b, c) {}

var bar = {};
foo.apply(bar, [1, 2, 3]); // array will expand to the below
foo.call(bar, 1, 2, 3); // results in a = 1, b = 2, c = 3

Quando utiliza-se os métodos call ou apply de Function.prototype, o valor de this dentro da função chamada irá referenciar explicitamente o primeiro argumento correspondente na chamada da função.

Como resultado, no exemplo anterior o escopo original do método não é aplicado, e this dentro de foo irá referenciar bar.

Erros comuns

Embora a maioria destes casos façam sentido, o primeiro pode ser considerado como um engano de concepção da linguagem, já que nunca se mostrou útil.

Foo.method = function() {
    function test() {
        // this referencia o objeto global
    }
    test();
};

Um erro comum é achar que this dentro de test referencia Foo; enquanto que, na realidade não é isto que acontece.

Com a finalidade de acessar Foo de dentro de test, é necessário instanciar uma variável global dentro do método para se referir à Foo.

Foo.method = function() {
    var that = this;
    function test() {
        // Utilize that no lugar de this aqui
    }
    test();
};

that trata-se de uma variável normal, porém é normalmente utilizada para referências externas de this. Quando combinadas com closures, também podem ser utilizadas para repassar this como valor.

Atribuindo métodos

Outra coisa que não funciona em JavaScript é function aliasing, ou seja, atribuir um método a uma variável.

var test = someObject.methodTest;
test();

Devido ao primeiro caso, test se comportará como uma chamada de função; como consequencia, o this dentro do método não apontará para someObject.

Enquanto que realizar binding do this pareça uma idéia ruim, no fundo, é o que faz a herança prototypal funcionar.

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

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

new Bar().method();

Quando os métodos da instância de Bar são chamados, o this faz referência àquela mesma instância.

Closures e Referências

Uma das caracterísricas mais poderosas do JavaScript é a possibilidade de usar closures. Quando usamos closures, definimos que um escopo sempre poderá acessar o escopo externo no qual foi definido. Uma vez que a única concepção de escopo em JavaScripe é function scope, todas as funções, por default, agem como closures.

Emulando variáveis privadas

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

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

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

Aqui, Counter retorna duas closures: a função 'increment' bem como a função 'get'. Ambas as funções mantêm uma referência ao escopo de 'Counter' e, portanto, sempre mantêm o acesso à variável 'count' definida naquele escopo.

Por que variáveis privadas funcionam

Uma vez que não é possível referenciar ou atribuir escopos em JavaScript, não existe uma maneira de acessar a variável 'count' por fora. A única maneira de interagir com a variável é através das duas closures.

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

O código acima não irá mudar a variável 'count' no escopo de 'Counter', uma vez que 'foo.hack' não foi definido naquele escopo. Neste caso, uma variável 'global' 'count' será criada ou substituida.

Closures dentro de laços

Um erro comum é utilizar closures dentro de laços, como se elas copiassem o valor da variável de indexação do laço.

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

O exemplo acima não retornará os números '0' até '9', mas os número '10' dez vezes.

A função anônima mantêm uma referência para 'i'. No momento em que 'console.log' é chamado, 'o laço for' já encerrou a execução, e o valor '10' está atrbuído em 'i'.

Com a finalidade de se obter o comportamento esperado, é necessário criar uma cópia do valor de 'i'.

Evitando problemas de referência

Com a finalidade de copiar o valor da variável de indexação do laço, a melhor opção é utilizar um wrapper anônimo.

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

A função anônima será chamada imediatamente com 'i' como seu primeiro argumento e receberá uma cópia do valor de 'i' como parâmetro de 'e'.

A função anônima que é passada para o 'setTimeout' agora possui uma referência a 'e', cujo os valores não são modificados pelo laço.

Não existe outra maneira de se obter este resultado, que não seja retornando uma função do wrapper anônimo que terá, então, o mesmo comportamento que o código acima.

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

Há ainda uma outra maneira, usando .bind, que pode ligar argumentos e um contexto'this' a uma função. Isto se comporta como o código acima

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

O objeto arguments

Todo escopo de uma função em JavaScript tem acesso à variável especial arguments. Esta variável armazena uma lista de todos os argumentos que foram passados para a função.

O objeto arguments não é um Array. Enquanto que ele possui uma semântica parecida com a de um array - a saber a propriedade length - ele não herda de Array.prototype e é de fato um Object.

Devido a isto, não é possível usar os métodos padrões de array como push, pop ou slice no arguments. Enquanto que a iteração com um simples for loop funciona bem, é necessário convertê-lo para um Array a fim de usar os métodos padrões de Array.

Convertendo em um Array

O código abaixo irá retornar um novo Array contendo todos os elementos do objeto arguments.

Array.prototype.slice.call(arguments);

Por este tipo de conversão ser lenta, seu uso em porções de código que apresentam performance crítica não é recomendado.

Passando argumentos

O código abaixo é a maneira recomendada de se passar argumentos de uma função para outra.

function foo() {
    bar.apply(null, arguments);
}
function bar(a, b, c) {
    // do stuff here
}

Outro truque é o de usar ambos call e apply juntos para criar wrappers.

function Foo() {}

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

// Create an unbound version of "method" 
// It takes the parameters: this, arg1, arg2...argN
Foo.method = function() {

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

Parâmetros formais Formal Parameters and Arguments Indices

O objeto arguments cria funções getter e setter para suas propriedades, bem como os parâmetros formais da função.

Como resultado, alterando o valor de um parâmetro formal também mudará o valor da propriedade correspondente no objeto 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);

Mitos e verdades sobre performance

A única vez em que o objeto arguments não é criado é quando é declarado como um nome dentro de uma função ou declarado como um de seus parâmetros formais. Não importa se ele é usado ou não.

Ambos getters e setters são sempre criados; desta maneira, usá-los não causa impacto de performance, especialmente não em código do mundo real, onde existe mais de um simples acesso às propriedades do objeto arguments.

Entretando, existe um caso em que a performance é drasticamente reduzida em engines modernas de JavaScript. Este caso é o uso de arguments.callee

function foo() {
    arguments.callee; // Faça alguma coisa com os objeto deta função
    arguments.callee.caller; // e o calling do objeto da função
}

function bigLoop() {
    for(var i = 0; i < 100000; i++) {
        foo(); // Would normally be inlined...
    }
}

Isto não somente acaba com possíveis ganhos de performance que resultariam de inlining, mas também quebram o encapsulamento pois a função agora depende de uma chamada específica de contexto.

O uso de arguments.callee é fortemente desencorajado.

Construtores

Construtores em JavaScript ainda são diferentes de muitas outras linguagens. Qualquer chamada a uma função que seja precedida pela palavra-chave new age como um cosntrutor.

Dentro do construtor - a função chamada - o valor de this se refere ao objeto recém criado. O prototype deste novo objeto é definido como o prototype do objeto da função que foi invocada como construtor.

Se a função chamada não possui um return statement explícito, então implicitamente retornará o valor de this - o novo objeto.

function Foo() {
    this.bla = 1;
}

Foo.prototype.test = function() {
    console.log(this.bla);
};

var test = new Foo();

O código acima chama Foo como construtor e define o prototype do objeto recém criado como Foo.prototype.

No caso de um return statement explícito, a função retorna o valor especificado pelo statement, mas somente se o valor de retorno for um Object.

function Bar() {
    return 2;
}
new Bar(); // um novo objeto

function Test() {
    this.value = 2;

    return {
        foo: 1
    };
}
new Test(); // o objeto retornado

Quando a palavra-chave new é omitida, a função não retornará o novo objeto.

function Foo() {
    this.bla = 1; // esta definida no escopo global do objeto
}
Foo(); // undefined

Enquanto que o exemplo acima pareça funcionar em alguns casos, devido a maneira como this funciona em JavaScript, o objeto global será usado como valor do this.

Fábricas

A fim de omitir a palavra-chave new, o construtor da função deve retornar um valor explicitamente.

function Bar() {
    var value = 1;
    return {
        method: function() {
            return value;
        }
    }
}
Bar.prototype = {
    foo: function() {}
};

new Bar();
Bar();

Ambas as chamadas a Bar retornam a mesma coisa, um objeto recém criado que tem a propriedade chamada method, que é uma Closure.

Deve-se perceber que a chamada new Bar() não afeta o prototype do objeto retornado. Enquanto o prototype é definido no objeto recém criado, Bar nunca retornará um novo objeto.

No exemplo acima, não existe diferença funcional entre o uso ou não de new.

Criando novos objetos por fábricas

É recomendado não usar new pois eventual o esquecimento de seu uso pode levar à defeitos.

A fim de criar um novo objeto, deve-se utilizar uma fábrica e construir o novo objeto dentro desta fábrica.

function Foo() {
    var obj = {};
    obj.value = 'blub';

    var private = 2;
    obj.someMethod = function(value) {
        this.value = value;
    }

    obj.getPrivate = function() {
        return private;
    }
    return obj;
}

Enquanto que o código acima previne defeitos decorrentes do esquecimnto da palavra-chave new e certamente utiliza-se de private variables de forma mais fácil, este apresenta algumas desvantagens:

  1. Utiliza mais memória desde que os objetos criados não compartilham métodos em um prototype.
  2. A fim de implementar herença, a fábrica precisa copiar todos os métodos de um outro objeto ou colocar o outro objeto no prototype do novo objeto.
  3. Quebrar a cadeia prototype somente por causa de esquecer eventualmente o new vai contra o que é proposto pela linguagem.

Conclusão

Enquanto que a omissão do new origine defeitos, não é certamente uma razão para quebrar a estrura prototype como um todo. No final, a melhor solução é sempre a que se adequa às necessidades de cada projeto. O importante é utilizar de forma consistente o modelo de criação de objetos escolhido.

Escopos e Namespaces

Embora o JavaScript lide bem com a sintaxe de duas chaves para definir blocos, ele não oferece suporte a escopos em blocos; por isso, todo o restante da linguagem é definido em escopo de função.

function test() { // define escopo
    for(var i = 0; i < 10; i++) { // não define escopo
        // count
    }
    console.log(i); // 10
}

Também não existem namespaces distintos em JavaScript, o que significa que tudo é automaticamente definido em um namespace globalmente compartilhado.

Cada vez que uma variável é referenciada, o JavaScript vai percorrer toda a hierarquia de escopos até encontrá-la. Caso ele alcance o escopo global e ainda não tenha encontrado a variável, ele lançará um ReferenceError.

A queda das variáveis globais

// script A
foo = '42';

// script B
var foo = '42'

O dois scripts acima não têm o mesmo efeito. O Script A define uma variável chamada foo no escopo global, e o Script B define foo no escopo atual.

Novamente, isto não é mesma coisa: emitir o uso de var tem várias implicações.

// escopo global
var foo = 42;
function test() {
    // escopo local
    foo = 21;
}
test();
foo; // 21

Deixando o statement var de fora da função test faz com que o valor de test seja sobrescrito. Enquanto que à primeira vista isto não pareça um problema, um script com milhares de linhas de código que não utiliza var apresenta erros horríveis e bugs difíceis de serem detectados.

// escopo global
var items = [/* uma lista qualquer */];
for(var i = 0; i < 10; i++) {
    subLoop();
}

function subLoop() {
    // escopo de subLoop
    for(i = 0; i < 10; i++) { // esquecendo do var statement
        // faça algo incrível!
    }
}

O loop externo terminará depois da primeira chamada para subLoop, uma vez que subLoop sobrescreve o valor global i. Utilizar var no segundo for loop evitaria facilmente este problema. O var statement nunca pode ser esquecido a não ser que o efeito desejado seja afetar o escopo externo.

Variáveis locais

A única fonte de variáveis locais em JavaScript são parâmetros de função e variáveis declaradas via var statement.

// escopo global
var foo = 1;
var bar = 2;
var i = 2;

function test(i) {
    // escopo local da função test
    i = 5;

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

Enquanto que foo e i são variáveis locais dentro do escopo da função test, a atribuição de bar irá substituir a variável global com o mesmo nome.

Hoisting

Javascript eleva declarações. Isto quer dizer que ambas declarações var e function serão movidas para o topo do escopo ao qual pertencem.

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];
    }
}

O código acima é modificado antes mesmo que seja executado. O JavaScript move todos as declarações var assim como as de function, para o topo do escopo mais próximo.

// declarações var são movidas aqui
var bar, someValue; // default para 'undefined'

// as declarações de função também são movidas
function test(data) {
    var goo, i, e; // escopo de bloco ausente move essas variáveis p/ cá
    if (false) {
        goo = 1;

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

bar(); // falha com um TypeError uma vez que bar continua 'undefined'
someValue = 42; // atribuições não são afetadas pelo hoisting
bar = function() {};

test();

A falta de delimitação de um escopo não somente moverá var statements para fora de loops e seus blocos, isto também fará com que os testes de determinados if se tornem não-intuitivos.

No código original, embora o if statement pareça modificar a variável global goo, ele modifica a variável local - depois que hoisting foi aplicado.

Por desconhecer o hoisting, pode-se suspeitar que o código abaixo lançaria um ReferenceError.

// verifique se SomeImportantThing já foi inicializado
if (!SomeImportantThing) {
    var SomeImportantThing = {};
}

Mas é claro, isto funciona devido ao fato de que var statement é movido para o topo do escopo global.

var SomeImportantThing;

// outro código pode inicializar SomeImportantThing aqui, ou não

// tenha certeza de que isto foi inicializado
if (!SomeImportantThing) {
    SomeImportantThing = {};
}

Ordem da Resolução de Nomes

Todos os escopos em JavaScript, incluindo o escopo global, possuem o this o qual faz referência ao objeto atual.

Escopos de funções também possuem o arguments, o qual contêm os argumentos que foram passados para a função.

Por exemplo, ao tentar acessar a variável denominada foo dentro do escopo de uma função, JavaScript irá procurar pela variável na seguinte ordem:

  1. No caso de haver var foo statement no escopo atual, use-a.
  2. Se um dos parâmetros é denominado foo, use-o.
  3. Se a própria função é denominada foo, use-a.
  4. Vá para o escopo externo mais próximo e inicie do #1.

Namespaces

Um problema comum relacionado ao fato de dispor de apenas um namespace global é a probabilidade de esbarrar com problemas onde nomes de variáveis coincidem. Em JavaScript, este problema pode ser facilmente evitado com a ajuda de wrappers anônimos.

(function() {
    // um "namespace" autocontido

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

})(); // execute a função imediatamente

Funções sem nome são consideradas expressões; a fim de ser referênciável, elas devem ser avaliadas.

( // avalie a função dentro dos parênteses
function() {}
) // e retorne o objeto de função
() // chama o resultado da avaliação

Existem outras maneiras para avaliar e chamar diretamente a expressão da função a qual, enquanto que diferentes em sintaxe, comportam-se da mesma maneira.

// Alguns outros estilos para invocar diretamente ...
!function(){}()
+function(){}()
(function(){}());
// e assim por diante...

Conclusão

É recomendado sempre utilizar um wrapper anônimo para encapsular código em seu próprio namespace. Isto não é somente uma maneira de se proteger contra conflitos de nomes, como também contribui para melhor modularização de programas.

Adicionalmente, o uso de variáveis globais é considerado uma prática ruim. Qualquer uso delas indica código mal escrito que tende à apresentar erros e apresenta manutenção complexa.

Arrays

Iteração com Arrays e propriedades

Embora arrays em JavaScript sejam objetos, não existem boas razões para utilizar o for in loop. De fato, existem muitas boas razões para evitar o uso de for in com arrays.

Uma vez que o for in loop enumera todas as propriedades que estão na cadeia prototype e visto que o único modo de excluir tais propriedades é por meio do uso do hasOwnProperty, ele chega a ser vinte vezes mais custoso que o uso normal do for loop.

Iteração

A fim de atingir a melhor performance ao interagir sobre arrays, a melhor opção é utilizar o clássico for loop.

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

Existe um detalhe importante no exemplo acima , que é o caching do comprimento do array via l = list.length.

Embora a propriedade length esteja definida no próprio array, ainda existe um trabalho extra ao executar a busca em cada iteração do array. Enquanto que recentes engines JavaScript talvez implementem um otimização para este caso, não existe uma maneira de saber quando o código será executado em uma dessas novas engines.

De fato, deixando de lado o armazenamento em caching pode resultar em um loop duas vezes mais rápido do que com o armazenamento em caching.

A propriedade length

Enquanto que o getter da propriedade length retorna o total de elementos que estão contidos no array, o setter pode ser usado para truncar o array.

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

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

Atribuir um valor de menor para length trunca o array. Por outro lado, incrementando o valor de length cria um array esparso.

Conclusão

Para melhor performance, é recomendado o uso do for loop e o cache da propriedade length. O uso do for in loop na iteração com array é um sinal de código mal escrito e tendencioso a apresentar defeitos, além de ter performance ruim.

O construtor Array

Uma vez que o construtor Array é ambíguo na forma como ele lida com seus parâmetros, o uso da notação [] é fortemente recomendado ao criar novo arrays.

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

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

Nos casos onde somente um argumento é passado para o construtor Array e quando o argumento é um Number, o construtor retornará um novo array sem elementos com a propriedade length configurada de acordo com o valor do argumento. É importante perceber que somente a propriedade length do novo array será configurada desta maneira; os índices do array não serão inicializados.

var arr = new Array(3);
arr[1]; // undefined
1 in arr; // false, o índice não foi definida

Ser capaz de definir o comprimento de um array antecipadamente é útil em poucos casos, como ao replicar uma string, em que se evita o uso de um loop.

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

Conclusão

O uso de literais é preferencial na inicialição de Arrays. São curtos, possuem uma sintaxe limpa, e contribuem para a legibilidade do código.

Tipos

Igualdades e comparações

JavaScript tem duas maneiras diferentes de comparar a igualdades entre valores de objetos.

O operador de igualdade

O operador de igualdade consiste de dois sinais de igual : ==

JavaScript é fracamente tipado. Isto que dizer que o operador de igualdade induz tipos ao invés de compará-los.

""           ==   "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

A tabela acima mostra o resultado da coerção de tipos, e isto é principal razão para que o uso == seja amplamente considerado uma má prática. Seu uso introduz defeitos difíceis de serem rastreados devido às suas complicadas regras de conversão.

Adicionalmente, também existe um impacto em performance quando a coerção acontece; por exemplo, é necessário que uma string seja convertida em um número antes que seja comparada com outro número.

O operador de igualdade estrito

O operador de igualdade estrito consiste de três sinais de igual : ===.

Ele funciona como o operador de igualdade normal, salvo que o operador de igualdade estrito não realiza coerção de tipos entre seus operandos.

""           ===   "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

Os resultados acima são bastante claros e permitem uma análise objetiva do código. Pode parecer complicar o código até um certo ponto mas também traz ganhos de performance em casos em que os operandos são de tipos diferentes.

Comparando Objetos

Enquanto que ambos == e === são denominados operadores de igualdade, eles se comportam de formas diferentes quando pelo menos um de seus operandos é um Object.

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

Aqui, ambos os operadores comparam em função da identidade e não da igualdade; isto é, eles vão comparar em função da mesma instância do objeto, muito parecido com o is do Python e a comparação de ponteiros em C.

Conclusão

E fortemente recomendado que só se use o operador de igualdade estrito. Em casos onde a coerção de tipos seja necessária, isto deve ser feito explicitamente e não deve ser deixado para as complicadas regras de coerção da linguagem.

O operador typeof

O operador typeof(em conjunto com instanceof é provavelmente a maior falha de design do JavaScript, por estar complemente mal implementado.

Embora instanceof ainda tenha seu uso limitado, typeof realmente só possui uma utilidade, a qual não acaba por ser a de verificar o tipo de um objeto.

A tabela de tipos em JavaScript

Value               Class      Type
-------------------------------------
"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

Na tabela acima, Type se refere ao valor de retorno do operador typeof. Como pode ser facilmente observado, este valor não é nada consistente.

O Class se refere ao valor interno da propriedade [[Class]] de um objeto.

A fim de se obeter o valor de [[Class]], deve-se utilizar o método toString de Object.prototype.

A classe de um objeto

A especificação fornece exatamente uma maneira de acessar o valor de [[Class]], com o uso de 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

No exemplo acima, Object.prototype.toString é chamado enquanto que o valor de this é definido como o objeto o qual o valor [[Class]] deva ser retornado.

Teste para variáveis não-definidas

typeof foo !== 'undefined'

O exemplo acima irá verificar se foo foi declarado ou não; apenas o fato de referênciá-lo poderia resultar em ReferenceError. Esta é a única utilidade real de typeof.

Conclusão

A fim de verificar o tipo de um objeto, é fortemente recomendade o uso de Object.prototype.toString pelo motivo de que esta é a única maneira confiável de ser feita. Como demonstrado na tabela anterior, alguns valores retornados de typeof não estão definidos na especificação; assim, eles podem variar entre implementações. O uso de typeof deve ser evitado, a menos que não se esteja testando se uma variável está ou não definida.

O operador instanceof

O operador instanceof compara os construtores de seus dois operandos. Ele é útil somente quando estamos comparando objetos personalizados. Quando utilizado em tipos nativos, ele é tão inútil quanto o operador typeof.

Comparando objetos personalizados

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

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

// Isto somente define Bar.prototype ao objeto de função Foo,
// mas não à instância atual de Foo
Bar.prototype = Foo;
new Bar() instanceof Foo; // false

Utilizando instanceof com tipos nativos

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

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

Uma coisa importante para perceber aqui é que instanceof não funciona em objetos originados de diferentes contextos de JavaScript (isto é, de diferentes documentos em um navegador web), uma vez que seus construtores não irão representar exatamente o mesmo objeto.

Conclusão

O operador instanceof deve somente ser utilizado quando estive lidando com objetos customizados originados de um mesmo contexto JavaScript. Bem como o operador typeof, qualquer outro uso de instanceof deve ser evitado.

Conversão de tipos

JavaScript é fracamente tipado, logo ele aplica a coerção de tipos sempre que possível.

// Estes retornam true
new Number(10) == 10; // Number.toString() é convertido
                      // de volta a um número

10 == '10';           // Strings são convertidas em Number
10 == '+10 ';         // Mais loucuras com strings
10 == '010';          // E mais 
isNaN(null) == false; // null é convertido em 0
                      // que claro não é NaN

// Estes retornam false
10 == 010;
10 == '-10';

A fim de evitar os problemas acima, o uso do operador de igualdade estrito é fortemente recomendado. Embora ele evite uma série de problemas comuns, existem ainda muitas outras questões que surgem do fraco sistema de tipagem do JavaScript.

Construtores de tipos nativos

Os construtores de tipos nativos como Number e String comportam-se diferentemente quando utilizados com ou sem a palavra-chave new.

new Number(10) === 10;     // False, Object e Number
Number(10) === 10;         // True, Number e Number
new Number(10) + 0 === 10; // True, devido à conversão implícita

Utilizar um tipo nativo como Number como construtor iré criar um novo objeto Number, porém omitir a palavra-chave new fará com que a função Number se comporte como um conversor.

Além, passando valores literais ou não-objetos irá resultar em mais coerções de tipos.

A melhor opção é converter para um dos três possíveis tipos de forma explícita.

Convertendo para String

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

Prefixando uma string vazia, qualquer valor pode ser facilmente convertido em uma string.

Convertendo para Number

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

Ao utilizar o operador de soma unário, é possível converter um valor para Number.

Convertendo para Boolean

Ao utilizar duas vezes o operador not, é possível converter um valor para Boolean.

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

Core

Por que não utilizar eval

A função eval executará uma string de código JavaScript no escopo local.

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

Entretanto, eval somente é executado no escopo local quando é chamado diretamente e quando o nome da função chamada é eval.

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

O uso de eval deve ser evitado. 99.9% de seu "uso" pode ser alcançado sem ele.

eval dissimulado

As funções timeout setTimeout e setInterval podem ambas receberem uma string como primeiro argumento. Tais strings sempre serão executadas no escopo global uma vez que eval não é chamado diretamente, naquele caso.

Problemas de segurança

eval também é considerado um problema de segurança, por que executa qualquer código dado. Ele nunca deve ser utilizado com strings de origens duvidosas ou desconhecidas.

Conclusão

eval nunca deve ser utilizado. Qualquer código que faça uso de eval seve ser questionado em sua utilidade, performance e segurança. Se algo necessita de eval para funcionar, então não deve ser utilizado. Um design melhor deve ser utilizado, um que não faça uso de eval.

undefined e null

JavaScript tem duas formas distintas para representar o nada, null e undefined, com o último sendo o mais útil.

O valor undefined

undefined é um tipo com um valor exato : undefined.

A linguagem também define uma variável global que tem como valor undefined; esta variável também é chamada undefined. Entretanto tal variável não é nem uma constante muito menos uma palavra-chave da linguagem. Isto significa que seu valor pode ser facilmente sobrescrito.

Aqui estão alguns exemplos de quando o valor undefined é retornado:

  • Acessando uma variável global (não-modificada) undefined.
  • Acessando uma variável declarada mas não ainda inicializada.
  • Retornos implícitos de funções devido ao esquecimento do return statement.
  • return statements que explicimente retornam o nada.
  • Busca por propriedades não-existentes.
  • Parâmetros de funções que não têm um valor explícito definido.
  • Qualquer coisa que tenha sido definida como undefined.
  • Qualquer expressão no formato void(expressão)

Manipulando mudanças no valor de undefined

Uma vez que a variável global undefined apenas mantém uma cópia do valor atual de undefined, atribuir-lhe um novo valor não muda o valor do tipo undefined.

Ainda, a fim de comparar alguma coisa com o valor de undefined, é necessário que primeiro se retorne o undefined.

A fim de proteger o código contra uma possível sobrescrtia da variável undefined, uma técnica comum utilizada é a de adicionar um parâmetro adicional em um wrapper anônimo que não recebe argumentos.

var undefined = 123;
(function(something, foo, undefined) {
    // undefined no escopo local agora 
    // refer-se ao valor `undefined`

})('Hello World', 42);

Outra maneira de atingir o mesmo efeito seria utilizar uma declaração dentro do wrapper.

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

})('Hello World', 42);

A única diferença aqui é a que a última versão resulta na redução de 4 bytes, e não existe outro var statement dentro do wrapper anônimo.

Usos do null

Enquanto que undefined no contexto da linguagem JavaScript é normalmente utilizado como um null, o atual null (ambos o tipo e o literal) é mais ou menos um outro tipo de dado.

Ele é utilizado internamente pelo JavaScript (como na declaração no fim da cadeia prototype ao definir Foo.prototype = null), porém na maioria dos casos, pode ser substituido por undefined.

Inserção automática do ponto e vírgula

Apesar do JavaScript possuir uma sintaxe no estilo C, o uso do ponto e vírgula não é obrigatório.

JavaScript não é uma linguagem 'semicolon-less'. De fato, o ponto e vírgula é necessário para o interpretação do código. Entretanto, o parser do JavaScript insere o ponto e vírgula automaticamente sempre que ocorrer um error de parser, decorrente da falta do ponto e vírgula.

var foo = function() {
} // parse error, semicolon expected
test()

A inserção acontece e o parser realiza uma nova tentativa.

var foo = function() {
}; // no error, parser continues
test()

A inseção automática de ponto e vírgula é considerada um dos maiores equívocos no design da linguagem pois pode influenciar no comportamento do código.

Como funciona

O código abaixo não possui ponto e vírgula, então fica à cargo do parser inserir o ponto e vírgula onde julgar necessário.

(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)

Abaixo está o resultado do processamento do parser.

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

        // Not inserted, lines got merged
        log('testing!')(options.list || []).forEach(function(i) {

        }); // <- inserted

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

        return; // <- inserted, breaks the return statement
        { // treated as a block

            // a label and a single expression statement
            foo: function() {} 
        }; // <- inserted
    }
    window.test = test; // <- inserted

// The lines got merged again
})(window)(function(window) {
    window.someLibrary = {}; // <- inserted

})(window); //<- inserted

O parser mudou o comportamento do código acima drásticamente. Em determinados casos, o parser não procede como o esperado.

Parênteses

No caso de parênteses, o parser não insere o ponto e vírgula.

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

Este código é interpretado em uma só linha.

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

As chances de log não retornar uma função são muito altas; portanto, o código acima irá produzir um TypeError informando que undefined is not a function.

Conclusão

É fortemente recomendado que nunca se omita o ponto e vírgula. Também é recomendado que chaves sejam mantidas na mesma linha que seus statements e que nunca sejam omitadas em declações de uma só linha como if / else statements. Tais medidas não somente melhorarão a consistência do código, como também irão previnir alteração no comportamento do código por má interpretação do parser do JavaScript.

O operador delete

Em resumo, é impossível remover variáveis globais, funções e outras coisas em JavaScript que tenham o atributo DontDelete definido.

Código global e código de função

Quando uma variável ou função é definida no escopo global ou em um escopo de função ela passa a ser uma propriedade de ambos objeto Activation e do objeto Global. Tais propriedades possuem um conjunto de atributos, um dos quais é o DontDelete. Declarações de funções e variáveis em código global e em código de função sempre criam propriedades com DontDelete, e portanto não podem ser removidas.

// variável global:
var a = 1; // DontDelete está definido
delete a; // false
a; // 1

// função comum:
function f() {} // DontDelete está definido
delete f; // false
typeof f; // "function"

// mudar o valor do atributo não ajuda:
f = 1;
delete f; // false
f; // 1

Propriedades explícitas

Propriedades definidas explicitamente podem ser apagadas normalmente.

// definição explícita de propriedade:
var obj = {x: 1};
obj.y = 2;
delete obj.x; // true
delete obj.y; // true
obj.x; // undefined
obj.y; // undefined

No exemplo acima, obj.x e obj.y podem ser removidos por que eles não possuem o atributo DontDelete. Este é o motivo pelo qual o exemplo abaixo também funciona.

// Desconsiderando o IE, isto funciona bem:
var GLOBAL_OBJECT = this;
GLOBAL_OBJECT.a = 1;
a === GLOBAL_OBJECT.a; // true - apenas uma variável global
delete GLOBAL_OBJECT.a; // true
GLOBAL_OBJECT.a; // undefined

Aqui nós utilizamos um truque para remover a. Aqui o this faz referência ao objeto Global e declara explicitamente a variável a como sua propriedade a qual nos permite removê-la.

O IE (pelo menos 6-8) possui defeitos, então o código acima não funciona.

Argumentos de função e propriedades nativas

Argumentos de função, objetos arguments e propriedades nativas tambêm possuem o DontDelete definido.

// argumentos de funções e propriedades:
(function (x) {

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

  delete x; // false
  x; // 1

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

})(1);

Objetos hosts

O comportamento do operador delete pode ser imprevisível para objetos hosts. Devido a especificação, objetos hosts têm permissão para implementar qualquer tipo de comportamento.

Conclusão

O operador delete freqüentemente apresenta um comportamento inesperado e só pode ser usado com segurança para remover propriedades definidas explicitamente em objetos normais.

Outros assuntos

setTimeout e setInterval

Uma vez que JavaScript é assíncrono, é possível agendar a execução de uma função usando as funções setTimeout e setInterval.

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

Quando setTimeout é chamado, ele retorna o ID do timeout e agenda a execução de foo para aproximadamente mil milissegundos no futuro. foo será executado uma única vez.

Dependendo de como a engine JavaScript que está rodando o código resolve o timer, bem como o fato de que o JavaScript é single threaded e outro código que é executado pode bloquear a thread, não há como garantir a precisão dos intervalos especificados nas chamadas setTimeout.

A função que foi passada como primeiro parâmetro será chamada pelo objeto global, o que significa que o this dentro da função chamada se refere ao objeto global.

function Foo() {
    this.value = 42;
    this.method = function() {
        // this faz referência ao objeto global
        console.log(this.value); // log undefined
    };
    setTimeout(this.method, 500);
}
new Foo();

Acumulando chamadas com o setInterval

Enquanto que setTimeout somente executa a função uma vez, setInterval - como o nome sugere - irá executar a função a cada X milisegundos, porém seu uso é desencorajado.

Quando um código em execução bloqueia a chamada do timeout, setInterval continuará emitindo chamadas para a função em questão. Isto pode, especialmente com intervalos curtos, resultar em uma pilha de chamadas de função.

function foo(){
    // algo que bloqueie por 1 segundo
}
setInterval(foo, 1000);

No código acima, foo será chamada uma vez e irá então bloquear a execução por um segundo.

Enquanto foo bloqueia a execução, setInterval irá programar mais chamadas para ela. Em seguida, quando foo completar sua execução, existirão dez chamadas programadas para ela aguardando por execução.

Lidando com possíveis bloqueios de código

A solução mais fácil, bem como a mais controlável, é usar setTimeout dentro da própria função.

function foo(){
    // Algo que bloqueia por um segundo
    setTimeout(foo, 1000);
}
foo();

Isto não somente encapsula a chamada para setTimeout, mas também previne o acumulo de chamadas e dá controle adicional. foo por si só pode decidir quando rodar novamente ou não.

Limpando Timeouts manualmente

A limpeza de intervalos e timeouts funciona passando o respectivo ID para clearTimeout ou clearInterval, dependendo onde a função set foi usada primeiro.

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

Limpando todos os Timeouts

Como não existe métodos próprios para limpar todos os timeouts e/ou intervalos, é necessário usar a força bruta para chegar a esta funcionalidade.

// limpe "todos" os timeouts
for(var i = 1; i < 1000; i++) {
    clearTimeout(i);
}

Mas ainda podem haver timeouts que não serão afetados por este número arbitrário. Uma outra maneira de fazer isto é considerar que o ID dado a um timeout é incrementado um a um cada vez que você chama setTimeout.

// limpe "todos" os timeouts
var biggestTimeoutId = window.setTimeout(function(){}, 1),
i;
for(i = 1; i <= biggestTimeoutId; i++) {
    clearTimeout(i);
}

Apesar desta maneira funcionar nos principais navegadores hoje em dia, não está especificado que os IDs respeitem uma ordem como este, logo esta ordem pode ser variada. Por este motivo, em vez disso é recomendade manter o controle de todos os IDs de timeouts, de forma que possam ser apagados precisamente.

O uso oculto do eval

setTimeout e setInterval aceitam uma string como primeiro argumento. Esta funcionalidade nunca deve ser utilizada pois internamente faz uso de eval.

function foo() {
    // será chamada
}

function bar() {
    function foo() {
        // nunca será chamada
    }
    setTimeout('foo()', 1000);
}
bar();

Uma vez que eval não é chamado diretamente neste caso, a string passada como argumento para setTimeout será executada no escopo global; assim, ela não usará a variável local foo do escopo de bar.

Também é recomendado não usar uma string para passar argumentos para a função que será chamada por qualquer uma das funções de timeout.

function foo(a, b, c) {}

// NUNCA use isto
setTimeout('foo(1, 2, 3)', 1000)

// Utilize uma função anônima do lugar
setTimeout(function() {
    foo(a, b, c);
}, 1000)

Conclusão

Uma string nunca deve ser usada como parâmetro setTimeout ou setInterval. Esta prática é um sinal claro de código ruim, quando argumentos precisam ser fornecido para a função que é chamada. Uma função anônima é que deve ser passada para que, em seguida, cuide da chamada.

Além disso, o uso de setInterval deve ser evitado pois seu scheduler não é bloqueado pela execução do JavaScript.