View on GitHub

Capacitacao-CEOS-2-Javascript

Escopo de Variáveis

Atenção: Esse capitulo é um pouco complexo, portanto, recomendo ler com calma, e fazer seus próprios testes com os exemplos. Também pode ser necessário reler o conteúdo.

Em diversas linguagens, assim como Javascript, existe um conceito muito importante: o escopo/contexto de execução.

Essas palavras são meio estranhas, mas tem um significado até que simples: escopo é onde nossas variáveis ficam e são acessíveis.

Talvez ainda esteja confuso, portanto:

Imagine uma “bolha” com as suas variáveis dentro dessa bolha. Isso é o escopo. É a “bolha” onde suas variáveis se encontram.

Por exemplo, no código abaixo:

let nome = "Fulano";
let idade = 15;
let apelido = "Zé";
let altura = 1.77;

if (altura > 1.10) {
    console.log(nome + ' tem altura maior que 1.10');
} else {
    console.log(nome + ' é baixinho, altura <= 1.10');
}

function mostraInformacoes() {
    console.log('Nome: ' +  nome);
    console.log('Idade: ' + idade);
    console.log('Apelido: ' + apelido);
    console.log('Altura: ' + altura); 
}

mostraInformacoes();

Execute o código acima e perceba que as variáveis nome, idade, apelido e altura estão acessíveis para todo o programa, inclusive dentro da função mostraInformacoes.

Isso acontece porque essas 4 variáveis estão no que chamamos de escopo global.

O escopo global é o nível mais alto de escopo que podemos ter. Existem outras dois escopos possívels em Javascript: escopo de funções e escopo de blocos.

Para criarmos uma variável no escopo global, basta declarar suas variáveis fora de qualquer função e blocos { }.

O escopo de funções é delimitado pelo corpo das nossas funções, ou seja, variáveis declaradas dentro de uma função pertencem ao escopo daquela função.

Veja o exemplo abaixo:

function um() {
    let x = 15;
    const y = 3.14;
}

function dois() {
    let z = 17;
    let palavraSecreta = "sacola";
    const permitido = true;
}

Vamos entender esse exemplo: no escopo da função um, temos duas variáveis: x e y. No escopo da função dois, temos três variáveis: z, palavraSecreta e permitido.

x e y só existem e podem ser acessadas dentro da função um.

z, palavraSecreta e permitido só existem e podem ser acessadas dentro da função dois.

Se tentassemos acessar palavraSecreta dentro da função um, teriamos um erro. Porque palavraSecreta só existe no escopo da função dois.

É possível visualizar o escopo dessas funções assim:

escopo1

Perceba que temos o escopo global, e dentro do escopo global, o escopo de cada função. Mais pra frente neste capítulo iremos entender melhor isso.

Mais um exemplo envolvendo escopo de funções:

let variavelUm = 10;
let variavelDois = "palavra";

function funcao(a, b) {
    let c = 11;
    let d = "string";
}

Perceba que nesse exemplo, temos no escopo global: as variáveis variavelUm e variavelDois, e também a função funcao. Pode parecer confuso, mas perceba que a função é declarada no escopo global, fora de qualquer tipo de bloco, portanto, ela está no escopo global.

Agora, no escopo da função funcao, temos as variáveis: os parâmetros a e b, e as variáveis c e d. Os parâmetros da função são acessíveis apenas dentro do escopo da função.

Nesse exemplo, temos variáveis no escopo global e no da função funcao. Podemos visualizar os escopos da seguinte forma:

> escopo global:
    variavelUm;
    variavelDois;
    funcao
    > escopo da função 'funcao':
        a;
        b;
        c;
        d;

Existe uma certa “hierarquia”, onde o escopo global está no topo dessa hierarquia.

Além do escopo de funções, temos o escopo de blocos.

O escopo de bloco é delimitado por um bloco Javascript, ou seja, está dentro de { }. Geralmente usamos { } para delimitar os blocos dos nossos if, else, while e for. Além das funções.

Variáveis declaradas com let e const dentro de um bloco ({ }), só são acessíveis dentro daquele bloco.

Por exemplo:

if (true) {
    let a = 15;
    const palavra = "pato";
}

As variáveis a e palavra só existem dentro desse bloco if. E não são acessiveis fora dele, isso acontece porque essas variáveis foram declaradas com let ou const dentro de um bloco, portanto, elas pertencem ao escopo desse bloco.

Essa forma de criar escopo é relativamente nova, pois foi introduzida com o ES6 (~2015), portanto, a forma mais comum de criar escopos novos é com funções.

Porém, é importante estar atento também ao escopo dos blocos.

Um detalhe importantissimo: o escopo de bloco só existe para variáveis declaradas com let ou const. Com var, a variável seria acessível no escopo global:

if (true) {
    var a = 15;
}

console.log(a);

Isso é um dos efeitos colaterais que o uso de var trás. Se exercutarmos o código abaixo:

if (true) {
    let a = 15;
}

console.log(a);

Teremos um ReferenceError: a is not defined, porque a só existe no escopo do bloco if (true) {...}.

Ok, resumindo: temos três escopos importantes para variáveis em Javascript:

Quando declaramos uma variável, ela estará em algum desses 3 escopos.

Quando vamos acessar ou modificar alguma variável, precisamos procurar nos escopos por essa variável, no caso, a busca é pelo nome da variável (também chamado de identificador).

Lembra quando falei que o escopo pode ser visto como uma bolha? Pois é, essa busca pela variável começa da bolha mais interna até a bolha mais externa, que no caso é o escopo global.

Em outras palavras, começamos a busca por uma variável no escopo onde ela foi usada, até o escopo global.

Vamos com um pequeno exemplo:

let a = 15;
function foo() {
    console.log(a);
}

Na chamada console.log(a), estamos acessando a variável a,então precisamos achar essa variável no escopo.

Verificamos primeiro se a está no escopo da função foo. No caso, não está, então procuramos no escopo acima, que nesse caso, é o escopo global. E então, achamos a no escopo global.

Nesse caso, o escopo está assim:

> escopo global:
    a;
    foo;
    > escopo da função 'foo':

Agora, um exemplo onde a está no escopo da função:

function foo() {
    let a = 15;
    console.log(a + 5);
}

Nesse caso, o escopo pode ser visualizado assim:

> escopo global:
    foo;
    > escopo da função 'foo':
        a;

Portanto, quando vamos buscar pelo nome a, procuramos e achamos direto no escopo da função foo.

Quando chegamos até o escopo global e não conseguimos achar o nome que estamos procurando, temos um ReferenceError, como no código abaixo:

function something() {
    console.log(x);
}

something();

Um último exemplo

function foo(a) {
    let b = a * 2;

    function bar() {
        let c = 42;

        console.log(c + b)
    }

    bar()
}

let a = 15;

function bla(b) {
    console.log(a + b)
}

foo(4);
bla(10);

Esse exemplo pode ser um pouco mais confuso: primeiro que temos uma função dentro de uma função, mas isso é perfeitamente ok, não existe nada que proiba ter uma função declarada dentro de uma função.

Agora, vamos entender como é feita a busca das variáveis:

O escopo está da seguinte forma:

> escopo global:
    a;
    bla;
    foo;
    > escopo da função 'bla':
        b;
    > escopo da função 'foo':
        a;
        b;
        bar;
        > escopo da função 'bar':
            c;

Assim, fica mais simples entender em qual escopo cada variável é achada:

Na expressão let b = a * 2;, a é um parâmetro da função foo, portanto está no escopo de foo, então, usamos esse a.

Na expressão console.log(c + b);, b não está no escopo de bar, portanto, olhamos pro escopo da função foo, e achamos b no escopo de foo, portanto, usamos ele.

Na expressão console.log(a + b);, a não está no escopo de bla, portanto, olhamos no escopo de cima, que nesse caso, é o escopo global, e temos um a no escopo global, então usamos esse a.

É importante notar que sempre vamos subindo do escopo mais interno pro mais externo, mas não olhamos no escopo de funções “vizinhas”.

Por exemplo, nesse código:

function x() {
  console.log(b);
}

function y() {
  let b = 15;
}

y();
x();

Esse código resulta em ReferenceError, pois embora exista b no escopo de y, esse b não pode ser acessado por x, pois a busca vai do escopo de x até o escopo mais externo, que nesse caso, já é o próprio escopo global.