Escopo

Close over (um problema com variáveis em laços)

javascript

No post anterior explicamos o que é Closure e como se comporta em relação ao escopo. O exemplo dado pelo W3C demonstrou que, com o uso de Closures, é possível manter o escopo de uma função acessível ao executá-la fora do seu escopo léxico.

Agora que possuímos o entendimento teórico, podemos iniciar a abordagem de exemplos práticos. Vamos começar avaliando  o código a seguir; você consegue prever o resultado?

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

Esperar que o código acima retorne 0,1,2,..,4 é um erro comum relacionado a má interpretação sobre o comportamento do compilador. Adiantando o resultado, o código acima retorna “5” cinco vezes. Alguns podem acabar pensando que isto é um defeito da engine já que, semanticamente, o código parece atender o que se propôs. Na realidade, o problema está no próprio código e não está relacionado em momento algum com a engine.

Analisando o código, fica evidente que cada chamada de função setTimeout acessa a mesma referência da variável i. Além deste ponto, setTimeout é executado após a execução do loop, ou seja, quando o valor de i é “5”.

Para resolver este problema precisamos que nossa chamada setTimeout seja executada de modo que enxergue o valor correto de i. Precisamos que cada callback de nosso timer tenha um escopo definido sobre cada execução do laço. Para isto, utilizaremos um recurso que nos permite executar uma função no momento em que a mesma é declarada. Isto é conhecido como Immediately-invoked function expression (IIFE). IIFE funciona criando um escopo ao declarar a função e logo em seguida executá-la. Vejamos o exemplo:

for(var i=0; i< 5; i++) {
    (function(j){
       setTimeout(function timer() {
            console.log(j);
       }, j*1000);
     })(i);
}

Com o uso da IIFE, conseguimos fechar o escopo de cada callback de timer sobre a execução do nosso laço. O valor de i fica guardado através da referência j, acessível neste escopo criado a cada execução. Desse modo, conseguimos referenciar corretamente os valores desejados para i.

O texto acima foi uma síntese do conteúdo do livro You Don’t Know JavaScript.

A declaração const

javascript

Vamos prolongar o assunto do post anterior e abordar a instrução const. Além do let, const é mais uma novidade que vem com ES6. Esta instrução é mais um recurso para abandonarmos os truques que simulam escopo em blocos. Assim como o let, a instrução const declara uma variável, restrita ao escopo o qual foi declarada. A diferença entre let e const é que a instrução  const define uma variável de valor fixo, ou seja, uma constante. Vejamos um exemplo :

if(true) {
   const b = 3;
   b = 4; // erro
}

console.log(b); // ReferenceError

Regras ao utilizar a instrução const :

  1. Ao declarar uma constante, você é obrigado a atribuir-lhe um valor;
  2. Você não pode declarar uma função ou uma variável que tenha a mesma denominação que sua constante em um mesmo escopo;
  3. Você não pode redeclarar uma constante;
  4. Você não pode reatribuir valores a uma constante.

Para mais informações sobre const visite CONST EM MDN.

Exemplos foram adaptados do livro You Don’t Know JavaScript.

A instrução LET

Até agora, vimos alguns conceitos de definição de escopo através de blocos. No post Blocos como escopo, vimos a importância de organizar o código com o propósito de facilitar a manutenção e sua legibilidade. Todos os recursos apresentados até agora são truques que agem sobre o comportamento padrão do Compilador. Até então, somente utilizando tais truques, era possível reproduzir o comportamento de escopo por blocos.

A instrução let é uma novidade do ES6. Esta nova instrução viabiliza a declaração de uma variável no escopo local de um determinado bloco. Vamos abordar o exemplo do post Blocos como escopo e substituir a instrução var por let :

var teste = true;
if (teste) {
let nome = 'Mario';
console.log(nome);
// mais código ...
}

console.log(nome); // Reference error

Com o uso da instrução let, a variável nome passou a pertencer ao escopo do If, ou seja, nome só existe dentro do contexto do if.

Diferenças entre let e var

Estudamos alguns comportamentos do Compilador em relação a instrução var. Pontualmente, estes comportamentos não serão os mesmos em relação ao let. Vamos analisar estes casos:

1 : JavaScript não eleva declarações de variáveis com a instrução let (em ECMAScript 6)

Sabemos que JavaScript eleva as declarações de variáveis com a instrução var. Isso quer dizer que independente de onde você tenha declarado a variável no código (considerando a cadeia de escopo), o Compilador realiza estas declarações antes que as demais instruções do seu programa sejam executadas. Lembrando que o que é elevado é a declaração da variável e não sua inicialização. Veja o exemplo:

function do_something() {
console.log(foo); // ReferenceError
let foo = 2;
}

Portanto, referenciar uma variável antes de sua declaração irá retornar ReferenceError.

2 : Redefinir uma variável dentro de um escopo de bloco retorna TypeError

Quando redefinimos uma variável com a instrução var, o compilador busca no escopo alguma variável com o mesmo identificador. Caso ele a encontre, a redefinição da variável não é realizada. Daí o seguinte exemplo funciona e não retorna erros :

if(true) {

var foo= 123;
var foo; // This works fine.

console.log(foo); // 123
}

Porém, o mesmo não funciona com a instrução let. A redefinição de uma variável com let não é realizada pelo Compilador, retornando um erro. Veja o exemplo:

if(true) {
let foo= 123;
let foo; // <span class="objectBox objectBox-errorMessage hasBreakSwitch "><span class="errorMessage ">TypeError: redeclaration of variable foo</span></span>

console.log(foo);
}

3 : Em um loop, variáveis declaradas com let pertencem somente ao escopo implícito do laço

No post Blocos como escopo, vimos que variáveis declaradas com a instrução var vazam do escopo implícito de um laço. Isso quer dizer que depois da execução do laço, a variável de contagem está registrada no escopo que envolve o laço. Veja o exemplo :

for(var i=0;i&lt;10;i++) {
//faça algo ...
}

alert(i); //retorna o valor 10

A instrução let altera este comportamento de modo que a variável i pertença ao escopo implícito do laço. Considere o exemplo abaixo :

for (let i = 0; i&lt;10; i++) {
console.log(i); // 0, 1, 2, 3, 4 ... 9
}

console.log(i); // i is not defined
Portanto, a instrução let nos deixa mais próximos de criar reais regras de acesso de variáveis em escopos definidos por blocos. Informe-se também sobre este novo recurso nas fontes abaixo:

Escopo e o uso de Eval

Vimos anteriormente que o escopo é definido de acordo com a estrutura proposta pelo programador, respeitando a ordem em que funções e variáveis foram declaradas. Vimos que funções possuem escopo próprio e variáveis contidas em uma função só podem ser acessadas através da própria função. Vimos também que variáveis no escopo global podem ser acessadas de qualquer ponto do código. Apesar do escopo ser definido pela ordem proposta pelo programador, JavaScript dispõe de recursos que permitem driblar esta definição. Um destes recursos é o eval.O método eval() executa qualquer código passado como string, ou seja, qualquer expressão passada como argumento será executada no momento em que eval for executado. Vejamos o exemplo:

function foo(str) {
    eval(str);
    console.log(a, b);
}

var b= 2;
foo("var b = 3;", 1)

A string “var b = 3” é interpretada no momento em que eval é executado. Desse modo, foo passa a conter uma variável b em seu escopo, que passa ser imediatamente a próxima referência à b na hierarquia do escopo, ao invés da declaração global de b. Portanto podemos concluir que houve uma intervenção no processo natural do escopo léxico. O uso de eval é desencorajado pois abre falhas de segurança e contribui para queda de performance.

O texto acima foi uma síntese do conteúdo do livro You Don’t Know JavaScript.

Blocos como escopo : a declaração “with”

Definimos escopo como o conjunto de regras que determinam como e quando as variáveis podem ser manipuladas. Em seguida, entendemos que o escopo léxico dita as regras de acesso de acordo com a ordem em que as variáveis foram dispostas pelo programador.

Em JavaScript, “with” é um recurso da linguagem capaz de driblar o escopo léxico, ou seja, independente da ordem de arranjo das variáveis, “with” tomará uma expressão passada como referência como seu escopo.
É a maneira nativa de estender o escopo de uma instrução, funcionando como um atalho para os acessos recorrentes à uma expressão.

Vamos estender o escopo de um dado objeto: respostas:

function foo(x, respostas) {
with (respostas) {
       x = 2; // Estamos atribuindo um novo valor à variável x dentro de o
}

if( (x + x) === respostas.x) { // 1 + 1 = 2 ?
       console.log('Sabemos somar');
}

console.log('Confira a resposta: ' + (x + x)); // Ooops ... retorna 4; isso pode não ser esperado
}

var o = {
       y : 2
};

f(1,o);

Caso o objeto passado por referência para o with não possua um dos atributos manipulados, o compilador elevará sua declaração para o escopo mais próximo na hierarquia, no nosso caso o escopo de foo. Nenhum aviso será lançado e a variável será alterada no escopo mais próximo. Caso este possua um identificador com a mesma denominação, o Compilador realizará uma atribuição (LHS: lefthand-side) e nosso valor no contexto de foo se perderá.

Podemos entender esta mudança de escopo e como o with dribla o escopo léxico a partir do seguinte esquema:

1 : Refere-se ao escopo global e as únicas referências são foo e o;

2 : Engloba o escopo de foo, o qual contêm a referência para obj;

3: Engloba o escopo do with; que é o próprio objeto obj;

Leia mais sobre este recurso em : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with

Atualmente, o uso de with é desencorajado.

O texto acima foi uma síntese do conteúdo do livro You Don’t Know JavaScript.

Blocos como escopo

No post anterior entendemos um pouco sobre escopo léxico e como o compilador interpreta a declaração das variáveis, de acordo com a ordem definida pelo programador. Com base no que foi apresentado até o momento, vamos introduzir a utilização de blocos como ferramenta para definição de escopo.

Apesar de funções serem a unidade padrão de definição de escopo em JavaScript, existem outras maneiras de definir escopo que podem nos auxiliar a escrever código limpo e de fácil manutenção.

Utilizamos blocos como escopo a fim de declarar variáveis dentro de um contexto, o mais próximo possível, de sua utilização. Considere o exemplo abaixo:

var teste = true;
if (teste) {
  var nome = 'Mario';
  nome = digaOla(nome);
  console.log(nome);
  // mais código ...
}

Estamos usando a variável “nome” somente no contexto do IF, logo declaramos ela dentro do IF. Porém não faz sentido utilizar var para este propósito, pois o compilador elevará sua declaração para o escopo mais próximo na hierarquia.

Ao definir um bloco como escopo, estamos estendendo o conceito “Principle of Least Privilege“, pois somente este é capaz de acessar o conjunto de recursos (variáveis e funções) necessários para seu propósito original.

Originalmente, JavaSript não oferece recursos para implementação de blocos como escopo. Considere o exemplo abaixo :

for(var i=0;i<10;i++) {
  //faça algo ...
}

alert(i); //retorna o valor 10

Apesar de i estar declarado no contexto do for, este é elevado para o escopo mais próximo da hierarquia e pode ser acessado em qualquer outra parte do código: a última linha retorna uma janela de alerta com 10. Este tipo de prática, nas mãos de programadores pouco cuidadosos, propicia a implementação de código de difícil manutenção.

Para ampliar a utilização de blocos como escopo em JavaScript, será necessário ir mais à fundo na linguagem. Os próximos posts estão reservados para estas técnicas e nuances.

O texto acima foi uma síntese do conteúdo do livro You Don’t Know JavaScript.

Escopo Léxico

No Post anterior definimos o que era escopo. Além disso, entendemos também o que era escopo de função. Agora podemos avançar e compreender o significado e propósito do Escopo Léxico.

Escopo léxico

Escopo Léxico é o modelo de definição de escopo empregado pelo JavaScript. Este escopo é definido na etapa de análise léxica, desempenhada pelo Compilador antes da execução do código. Portando, ele respeita a ordem em que os blocos e as variáveis foram escritas pelo programador.

Vamos analisar o exemplo abaixo:


function sucessor(a) {

 var b = a + 1;

 function imprimir(c) {
  alert('Buscando sucessores de ' + a);
  alert('Sucessor de ' + a + ' : ' + b);
  alert('Sucessor de ' + b + ' : ' + c);
 }

 imprimir(b + 1);
}

sucessor(1);

O exemplo acima demonstra como JavaScript se comporta em casos de bloco de escopo aninhados; a função “sucessor” está definida no escopo global. Em seguida, agora no escopo de “sucessor”, temos os identificadores, a, b e imprimir. Por último, no escopo de “imprimir”, temos acesso à a, b e a inclusão do identificador c.

Quando trabalhamos com escopo léxico, não há como definir um mesmo escopo em diferentes partes do código. A definição ocorre na análise léxica e segue o aninhamento definido pelo programador.

Escopo em JavaScript

No mercado, JavaScript ganhou a importância que merecia. Hoje temos papéis de profissionais Front End bem definidos. A interface está bem desacoplada do Back End e pode ser interpretada integralmente no cliente.

Para trabalhar com frameworks e ferramentas que pertençam à esta nova realidade, torna-se mais que necessário entender JavaScript profissionalmente. Para tal, alguns conceitos e nuances da linguagem devem ser muito bem compreendidos para que o desenvolvedor execute o trabalho de forma eficiente.

Escopo

Em JavaScript, você pode entender que um escopo é como um conjunto capaz de reunir variáveis, objetos e até mesmo funções. Esta é uma definição básica que será aprimorada ao final do texto.

Dependendo de como foi definido, um escopo pode ser acessado em outros lugares do código. Isso é possível pois JavaScript tem algo chamado de escopo de função.

Escopo de Função

Toda função em JavaScript tem seu próprio escopo. Portanto, uma função define um novo conjunto de elementos (variáveis, objetos e até funções). Vamos explorar, considere o exemplo:

var passaro = 'Sabiá';

function gaiola() {
	var passaro = 'Canário';
	return passaro;
}

alert(passaro); // Sabiá
alert(gaiola()); // Canário

No exemplo, apesar de existirem duas variáveis “passaro”,  elas são variáveis independentes. Vamos analisar a primeira ocorrência:

var passaro = 'Sabiá';

Neste momento, o Compilador varre o escopo atual em busca de uma variável denominada “passaro”.
Esta, por sua vez, ainda não foi declara e portanto é necessário que o Compilador a declare no escopo em questão, que no momento trata-se do escopo global. Uma variável no escopo global pode ser acessada em qualquer lugar do código, a qualquer momento. No nosso caso, “Sabiá” é a referência que se encontra livre.

Vamos partir para a segunda ocorrência:

function gaiola() {
	var passaro = 'Canário';
	return passaro;
}

Mais uma vez o Compilador varre o escopo atual em busca de uma variável denominada “passaro”. Talvez você imagine que neste momento o Compilador irá encontrar a variável já declarada, conforme exposto no passo anterior. Pois bem, aqui acontece algo diferente. Lembre-se que o Compilador busca por variáveis sempre no escopo em questão. No início do texto nós afirmamos que qualquer função, em JavaScript, define um escopo. Desse modo, podemos concluir que o escopo o qual o Compilador esta processando é o escopo de “gaiola”, pois “gaiola” é uma função e possui seu próprio escopo.

Assim, quando executamos:

// (... código anterior)

alert(passaro); // Sabiá
alert(gaiola()); // Canário

Temos “Sabiá” disponível no escopo global e “Canário” acessível somente por “gaiola()”. Desse modo, podemos definir “escopo” como um conjunto de regras que agem sobre como a Engine do JavaScript procura e acessa variáveis.