Uma closure é uma função contruída com referências ao escopo externo a ela, o que permite acessar essas referências. Esse padrão permite que variáveis de um escopo externo seja utilizados dentro de um escopo que esteja alinhado a ele.
Em Jajavscript, cada funcão executada, bloco de código {}
e script (global) possuem um objeto interno denominado como Lexical Enviromnent. Esse Lexical Enviroment possui duas propriedades principais:
- Um objeto que armazena todas as variáveis locais como propriedades, bem como o valor de this;
- Uma referência para outro Lexical Enviromnent mais externo a ele.
Diferente de outros objetos do Javascript, o Lexical Enviromnent existe apenas internamente. Não temos acesso a ele em nosso código de modo que possamos manipulá-lo diretamente.
Lexical Enviromnent para variáveis
No caso de variáveis, elas nada mais são que propriedades do Lexical Enviromnent. Mudar seu valor significa mudar o valor dessas propriedades. Segue um exemplo de criação de variável no escopo do script (global).
Exemplo:
/* Inicio da execucao: aqui e criado o Lexical Enviromnent do script
Lexical Enviromnent:
- name: <uninitialized>
- outer: null
*/
let name;
/*
Lexical Enviromnent:
- name: undefined
- outer: null
*/
name = 'Maria'
/*
Lexical Enviromnent:
- name: 'Maria'
- outer: null
*/
No exemplo acima, podemos perceber dois detalhes interessantes:
- A variável
name
já era conhecida pelo scritp antes de ser declarada. Como visto na sessão hoisting. - A propriedade outer esta como null devido ao fato do escopo do código ser o escopo do script ou global.
Lexical Enviromnent para funcões
Diferente do que ocorre com variáveis, a declaração de uma funçãio é totalmente inicializada.
Exemplo:
/* Inicio da execucao: aqui e criado o Lexical Enviromnent do script
Lexical Enviromnent:
- name: <uninitialized>
- printName: function
- outer: null
*/
let name;
function printName() {
console.log(name);
}
/*
Lexical Enviromnent:
- name: undefined
- printName: function
- outer: null
*/
Esse comportamente não ocorre para casos envolvendo expressões com funções, como por exemplo:
let print = printName(name) { ... }
O Lexical Enviromnent do escopo interno dasd funções é criado somente no momento da execução das funcões, no inicio da sua chamada. Ainda seguindo o exemplo anterior, ao chamarmos a função printName
, teremos o seguinte caso:
/* Inicio da execucao: aqui e criado o Lexical Enviromnent do script
Lexical Enviromnent (global):
- name: <uninitialized>
- printName: function
- outer: null
*/
let name = 'Maria';
function printName() {
console.log(name);
}
/*
Lexical Enviromnent (global):
- name: 'Maria'
- printName: function
- outer: null
*/
printName();
/*
Lexical Enviromnent (global):
- name: 'Maria'
- printName: function
- outer: null
Lexical Enviromnent (printName):
- outer: 'Lexical Enviromnent (global)'
*/
Ao tentar acessar a variável name
, a função printName
irá buscar a variável name
em seu próprio Lexical Enviroment. Não a encontrado, ele irá buscar no Lexical Enviromnent externo imediato outer, e assim sucessivamente até o alcançar o Lexical Envirmnent global do script. Caso não encontre a variável, o Javascript retornará um erro.
Umam variável é atualizada no Lexical Enviromnent ao qual ela pertence.
Exemplo:
function makeCounter() {
let counter = 0;
return function() {
return counter++;
}
}
let counter = makeCounter();
/*
Lexical Enviromnent (global):
- counter: makeCounter()
- makeCounter: function
- outer: null
Lexical Enviromnent (makeCounter):
- outer: 'Lexical Enviromnent (global)'
- counter: 0
*/
console.log(counter()); // 1
/*
Lexical Enviromnent (counter()):
- outer: 'Lexical Enviromnent (makeCounter)'
- counter: 1
*/
console.log(counter()); // 2
/*
Lexical Enviromnent (counter()):
- outer: 'Lexical Enviromnent (makeCounter)'
- counter: 2
*/
console.log(counter()); // 3
Garbage Collection
O Javascript possui um um garbage collector que remove todas as referências do Lexical Enviromnent de uma função após a mesma ser executada. Isso ocorre porque após a execução não há mais referências a ela. Entretanto, caso a função possua uma outra função aninhada a ela, o Lexical Enviromnent será mantido, porque o Lexical Enviromnent dessa função aninhada ainda faz referência ao Lexical Enviromnent da função superior a ela.
Exemplo:
// Funcao simples
function printHello() {
console.log('Hello!')
}
// Apos a execucao, o Lexical Enviromnent dessa funcao
// sera desalocado da memoria
printHello();
// Funcao com outra funcao interna
function makeCounter() {
let counter = 0;
return function() {
return counter++;
}
}
/* Apos a execucao, o Lexical Enviromnent de 'MakeCounter'
ainda permacera em memoria porque ele ainda e apontado pela
propriedade 'outer' da funcao interna e ainda é referenciada
variavel 'counter'
*/
let counter = makeCounter();
/*
Lexical Enviromnent (makeCounter):
- outer: 'Lexical Enviromnent (global)'
- counter: 0
Lexical Enviromnent (function interna):
- outer: : 'Lexical Enviromnent (makeCounter)'
*/
counter();