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
}
Nota: Quando não usado em um assignment, em um statement return ou como um argumento de função, a notação
{...}será interpretada como um block statement e não como um objeto literal. Isto, em conjunto com a inserção automática de ponto-e-vírgula, pode levar à erros sutis.
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.
// 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.
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.
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 = {};
}
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:
- No caso de haver
var foostatement no escopo atual, use-a. - Se um dos parâmetros é denominado
foo, use-o. - Se a própria função é denominada
foo, use-a. - Vá para o escopo externo mais próximo e inicie do #1.
Nota: Dispor de um parâmetro denominado
argumentsirá previnir a criação do objeto defaultarguments.
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...
É 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.