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.

Closures

Entender o conceito de Closures é um passo importante para aqueles que estudam JavaScript. Depois de acompanhar e compreender o significado de escopo e como o escopo léxico funciona, o caminho naturalmente segue para as Closures. Caso você nunca tenha tido contato (ou pensa que não teve) com Closures, pode pensar que se trata de uma ferramenta com sintaxe diferenciada; ao progredir com o entendimento vamos desmistificar este conceito e mostrar que Closures podem estar tão arraigadas no JavaScript que acabam por passar despercebidas por aquele que ainda não as conhece.

Antes de apresentar a definição formal de Closures, vamos exercitar alguns problemas. Existem muitos exemplos na internet sobre Closures. Apesar de gostar muito do conteúdo da MDN, vou abordar o exemplo do W3C por ser mais simples e objetivo. Trata-se de problema comum: escrever uma função que soma uma unidade a um contador. A primeira proposta para resolver o problema inclui a declaração de uma variável em escopo global:

var counter = 0; 
function add() { 
    counter += 1; 
} 
add(); 
add(); 
add(); // a variável counter agora guarda 3

O código acima funciona e resolve o problema. O problema é que a variável counter está definida no escopo global, ou seja, pode ser modificada por outras vias além da função add. A fim de resolver o problema podemos pensar, imediatamente, em mover counter para dentro de add, restringindo seu acesso por meio das regras que aprendemos sobre o escopo léxico. Assim temos o seguinte código :

function add() {
    var counter = 0;
    counter += 1;
}

add();
add();
add();

// the counter should now be 3, but it does not work !

Apesar de resolvermos o problema com o escopo global, agora nosso código não funciona como precisamos. Sempre que chamamos por add, counter é definido e inicializado com zero. Isso acontece por que nosso escopo não é persistido ou mantido quando a função add encerra sua execução, ou seja, tudo que pertence a ele é perdido. Portanto, cada chamada é uma nova execução legítima, independente das execuções anteriores. Então, agora precisamos pensar em como criar esta dependência, ou seja, manter o escopo na memória e relacioná-lo com as chamadas seguintes.

Antes de prosseguir, vamos conhecer a definição formal de Closure :

Closure é um recurso capaz de persistir o escopo de uma função, provendo acesso ao seu escopo léxico mesmo quando esta mesma função é executada fora do seu escopo léxico.

Pode parecer um pouco confuso mas nosso exemplo está caminhando para a definição acima. O que precisamos é que o escopo de add não seja perdido, Para isto, podemos declarar uma função dentro de add. Assim reservamos as declarações e inicializações em add e a manipulação neste segundo escopo da hierarquia.

Desse modo, a variável counter seria declarada e inicializada somente uma vez. Vamos ver o exemplo :

function add() {
    var counter = 0;
    function plus() {counter += 1;}
    plus();
    return counter;
}

Funciona, mas só uma vez. Nós não temos acesso a função plus em add. Desse modo, ainda é necessário chamar pela função add se quisermos somar uma unidade em counter. Sendo assim, voltamos ao problema anterior, pois counter se perderá e será novamente declarado e inicializado.

Como acessar plus mesmo depois de add ter sido executado? Para responder esta pergunta vamos analisar a proposta abaixo:

var add = (function () {
    var counter = 0;
    return function () {return counter += 1;}
})(); // self-invoking function

add();
add();
add();

// the counter is now 3

Agora add é uma variável declarada com var e seu valor de inicialização é uma função anônima, a qual é imediatamente executada em virtude da notação (function() { …})(). Essa estratégia permite acessar a função plus de dentro de add; neste caso não temos add como identificador da função onde counter é declarado, mas sim como identificador do que é retornado pela função anônima. A instrução return devolve uma função que guarda consigo o escopo onde foi declarada. Essa função retornada é a nossa antiga plus, agora acessível via add() e fora do seu escopo léxico. Temos assim uma Closure.

O tutorial original é da W3C : JavaScript Closures

Existe um material mais completo na MDN : Closures

A declaração const

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.

Novidades no blog

Olá pessoal.

2015… ano novo… novidades no JavaNiaco… pelo menos assim espero :)

Depois de um pouco mais de 3 anos desde nosso último post (uou…quanto tempo :s)… vamos (sim… vamos… essa é uma das novidades) tentar tirar as teias de aranha do blog e voltar aos poucos a postar alguma coisa, mesmo que vez ou outra. Mas antes disso, aproveito para publicar algumas novidades. São as seguintes:

  • Bem, antes de mais nada, vou começar por uma dívida antiga. A criação de um repositório para que os códigos utilizados nos nossos exemplos possam ser baixados por qualquer pessoa. Os posts novos só serão publicados a partir do momento em que o código já estiver no repositório, já os antigos, serão commitados e os posts atualizados com seus respectivos links. Sugestões e correções são sempre bem-vindas e como o repositório é aberto, commits também podem ser feitos*. O endereço do nosso repositório é http://github.com/JavaNiaco.
  • A segunda novidade é que agora temos mais um integrante. Uma das melhores dev de front que conheço e também minha amiga, @angellicaaraujo, aceitou meu convite e agora é uma colaboradora do nosso blog. Tenho certeza que os posts dela irão agregar muito conhecimento e certamente ajudarão muitas pessoas no dia-a-dia do desenvolvimento de softwares. Seja bem-vinda. =)

*Qualquer dúvida sobre como colaborar com o código do nosso blog, o Bootcamp do Github ensina como fazer todo o passo-a-passo.

Seguir

Obtenha todo post novo entregue na sua caixa de entrada.

Junte-se a 58 outros seguidores