Skip to content

Latest commit

 

History

History
225 lines (165 loc) · 7.39 KB

File metadata and controls

225 lines (165 loc) · 7.39 KB

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
}

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.

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.

Nota: Dispor de um parâmetro denominado arguments irá previnir a criação do objeto default arguments.

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.