Nomes, vinculação e escopo
Em programas de computador, nomes representam coisas, como valores, comportamentos etc. Os nomes são vinculados às coisas que representam em diversos momentos. Por exemplo:
- O nome
if
é vinculado ao comportamento de estrutura de seleção em tempo de projeto de linguagem - Em C, quando você escreve
#define PI 3.14159
, o nomePI
é vinculado ao valor3.14159
em tempo de compilação - Em geral, variáveis são vinculadas aos seus valores em tempo de execução
Em linguagens de programação, vinculação (ou amarração, ou ligação, ou binding) se refere à vinculação de um nome a um valor ou comportamento.
Escopo de uma vinculação é o conjunto de trechos de um programa que consegue usar a vinculação. Exemplo:
var glob = 123; // 1
console.log(glob); // 2
function teste() { // 3
var x = 2; // 4
console.log(x); // 5
} // 6
glob = 456; // 7
O escopo da variável glob
corresponde às linhas 2, 4, 5 e 7 do programa. O escopo da variável x
corresponde à linha 5.
A variável glob
tem um escopo global, pois é acessível em todo o programa (essa é uma definição simplificada). A variável x
tem um escopo local, pois só é acessível dentro da função no qual ela é definida. A variável teste
também possui escopo global (lembre-se de que em Javascript funções também são valores!).
A maioria das linguagens de programação adota regras de escopo léxico (ou estático). Isso significa que é possível determinar o escopo de uma variável somente lendo o código-fonte do programa. Algumas linguagens de programação usam o escopo dinâmico; uma situação comum nesse caso é uma função ter acesso a variáveis definidas na função que a chamou. Pode-se argumentar que uma linguagem de programação que adota o escopo dinâmico tende a resultar em programas mais difíceis de entender.
O contexto ou ambiente de referenciamento de uma região de um programa corresponde ao conjunto de vinculações que pode-se acessar naquela região. Vejamos o exemplo anterior:
var glob = 123; // 1
console.log(glob); // 2
function teste() { // 3
var x = 2; // 4
console.log(x); // 5
} // 6
glob = 456; // 7
O ambiente de referenciamento da linha 7 corresponde às variáveis glob
e teste
(e, naturalmente, outras variáveis definidas globalmente pelo browser, a exemplo de window
e console
). O ambiente de referenciamento da linha 5 contém todas essas variáveis e mais a variável x
.
Regras de escopo de Javascript
Considere agora o exemplo a seguir. O que acontecerá quando a linha 8 for executada? Vai dar erro? Para responder a essa pergunta, devemos descobrir se a variável xext
faz ou não faz parte do ambiente de referenciamento da linha 8 (e da função interna
, em geral). Faça suas apostas e execute o código.
function(str, info) { return simplesEval(str, info); }
Para buscar da variável xint
, o interpretador Javascript primeiro procura nas variáveis definidas dentro da função interna
. Não encontrando, ele vai procurar variáveis definidas no contexto da função externa
. Se ainda não encontrar, procura nas variáveis globais. Ou seja, o ambiente de referenciamento inclui o escopo atual e mais todos os escopos externos.
Então o que vai acontecer no seguinte código?
function(str, info) { return simplesEval(str, info); }
Existem três vinculações diferentes do nome x
, com diferentes escopos. Surge a dúvida: se o x
da linha 1 tem escopo global, isso significa que esse escopo se estende à linha 8?
Não. A declaração de x
na linha 4 mascara a variável x
da linha 1 (esse fenômeno é também conhecido como sombreamento. O resultado disso é que, dentro da função externa
, vale a variável x
da linha 4
… exceto na linha 8, pois nesse caso a variável que vale é a variável x
definida na linha 7.
No final das contas, o escopo de x
da linha 1 é a linha 12; o escopo de x
da linha 4 é a linha 10; e o escopo de x
da linha 7 é a linha 8.
Closures
(Baseado em https://developer.mozilla.org/en/docs/Web/JavaScript/Closures)
Closures (do inglês, fechamento) são funções que referenciam variáveis independentes (livres). Em outras palavras, a função definida na closure lembra-se do ambiente no qual ele foi criado.
Vamos começar com um exemplo simples, sem closure:
function(str, info) { return simplesEval(str, info); }
Agora um exemplo mais complexo, com closure:
function(str, info) { return simplesEval(str, info); }
O resultado é o mesmo, mas a função criaFuncaoAlo
desta vez retorna a função interna mostraNome
, sem executá-la. Em vez disso, a função só é executada na última linha de código.
O código não é muito intuitivo. Normalmente, uma variável local definida em uma função só existe enquanto a função é executada; quando a execução termina, a variável deveria ser destruída e deixar de ser acessível. No exemplo, no entanto, a variável nome
ainda está acessível depois que a função criaFuncaoAlo
terminou de executar.
A solução desse mistério é que mostraNome
tornou-se uma closure, que é um objeto especial que combina duas coisas: uma função e o ambiente na qual a função foi criada. O ambiente consiste de todas as variáveis locais disponíveis no momento em que a função foi criada. Dizemos que a função mostraNome
captura a variável nome.
closure mostraNome =
função mostraNome +
variável nome
Mais um exemplo:
function(str, info) { return simplesEval(str, info); }
Qual o resultado? Três números iguais? Três números diferentes? O primeiro número será igual ao terceiro? Faça suas apostas, execute e revise seu conhecimento sobre closures.
Agora um exemplo mais útil, mostrando que parâmetros também são capturados por uma closure:
function(str, info) { return simplesEval(str, info); }
A closure também pode alterar as variáveis capturadas:
function(str, info) { return simplesEval(str, info); }
Closures e programação orientada a objetos
Uma closure permite associar alguns dados (o ambiente) com a função que opera sobre os dados. Isso soa como programação orientada a objetos, na qual objetos permitem associar alguns dados (as propriedades do objeto) a um ou mais métodos.
Exemplo:
function(str, info) { return simplesEval(str, info); }
Obviamente, esse esquema é limitado, pois não há herança e polimorfismo.
Closures na prática (programação web)
É importante entender closures para programar Javascript para web? Muito! Grande parte do código que escrevemos em Javascript para web é baseado em eventos: definimos um comportamento, então anexamos o comportamento a um evento que é disparado pelo usuário (por exemplo, um clique). O código é geralmente escrito em um callback: uma função que é executada em resposta a um evento.
Exemplo (rode o código e depois clique no botão):
function(str, info) { return simplesEval(str, info); }
Agora vamos fazer com três botões?
function(str, info) { return simplesEval(str, info); }
Funcionou como você gostaria? Qual a explicação?
Como resolver esse problema? A solução é capturar o índice i
em uma função retornada dentro de outra função:
function(str, info) { return simplesEval(str, info); }
Podemos fazer a mesma coisa sem criar uma função auxiliar (basta trocar criaFuncaoAlerta
pela sua implementação como função anônima):
function(str, info) { return simplesEval(str, info); }
Note que definimos uma função anônima e chamamos essa função imediatamente. Esse é um padrão tão comum no desenvolvimento web em JavaScript que tem até um nome: IIFE (immediately-invoked function expression, ou expressão de função invocada imediatamente).