Esta página foi traduzida do inglês pela comunidade.Saiba mais e junte-se à comunidade MDN Web Docs.
Closures
Umaclosure é a combinação de uma função com as referências ao estado que a circunda (oambiente léxico). Em outras palavras, uma closure lhe dá acesso ao escopo de uma função externa a partir de uma função interna. Em JavaScript, as closures são criadas toda vez que uma função é criada, no momento da criação da função.
In this article
Escopo léxico
Considere a função abaixo:
function init() { var name = "Mozilla"; // name é uma variável local criada pelo init function displayName() { // displayName() é a função interna, uma closure console.log(name); // usa a variável declarada na função pai } displayName();}init();A funçãoinit() cria uma variável local chamadaname, e depois define uma função chamadadisplayName().displayName() é uma função aninhada (umaclosure) — ela é definida dentro da funçãoinit(), e está disponivel apenas dentro do corpo daquela função. Diferente de init(),displayName() não tem variáveis locais próprias, e ao invés disso reusa a variávelname declarada na função pai.
Rode o código e veja que isso funciona. Este é um exemplo deescopo léxico: em JavaScript, o escopo de uma variável é definido por sua localização dentro do código fonte (isto é aparentementeléxico) e funções aninhadas têm acesso às variáveis declaradas em seu escopo externo.
Closure
Agora considere o seguinte exemplo:
function makeFunc() { var name = "Mozilla"; function displayName() { alert(name); } return displayName;}var myFunc = makeFunc();myFunc();Se você rodar este código o mesmo terá exatamente o mesmo efeito que oinit() do exemplo anterior: a palavra "Mozilla" será mostrada na caixa de alerta. O que é diferente - e interessante - é o fato de que a função interna dodisplayName() foi retornada da função externa antes de ser executada.
Pode parecer não muito intuitivo de que o código de fato funciona. Normalmente variáveis locais de uma função, apenas existem pela duração de sua execução. Uma vez quemakeFunc() terminou de executar, é razoável esperar que a variávelname não será mais necessária. Dado que o código ainda funciona como o esperado, este não é o caso.
A solução para tal problema é que a funçãomyFunc tornou-se umaclosure. Uma closure trata-se de um tipo especial de objeto que combina duas coisas: a função e o ambiente onde a função foi criada. Este ambiente consiste de quaisquer variáveis que estavam no escopo naquele momento em que a função foi criada. Neste caso,myFunc é a closure que incorpora tanto a funçãodisplayName quanto a palavraMozilla que existia quando a closure foi criada.
Aqui temos um exemplo um pouco mais interessante, a funçãomakeAdder:
function makeAdder(x) { return function (y) { return x + y; };}var add5 = makeAdder(5);var add10 = makeAdder(10);print(add5(2)); // 7print(add10(2)); // 12Neste exemplo definimos a funçãomakeAdder(x) que toma um único argumentox e retorna uma nova função. A função retornada toma então um único argumento,y, e retorna então a soma dex e dey.
Na essência omakeAdder trata-se de umafunção fábrica - irá construir outras funções que podem adicionar um determinado valor específico a seu argumento. No exemplo acima usamos a fábrica de funções para criar duas novas funções - uma que adiciona 5 ao argumento, e outra que adiciona 10.
Ambas as funçõesadd5 eadd10 são closures. Compartilham o mesmo corpo de definição de função mas armazenam diferentes ambientes. No ambiente daadd5, por exemplo,x equivale a 5, enquanto naadd10 o valor de x é 10.
Closures na prática
Esta é a teoria — mas closures são realmente úteis? Vamos considerar suas aplicações práticas. Uma closure deixa você associar dados (do ambiente) com uma função que trabalha estes dados. Isto está diretamente ligado com programação orientada a objetos, onde objetos nos permitem associar dados (as propriedades do objeto) utilizando um ou mais métodos.
Consequentemente, você pode utilizar uma closure em qualquer lugar onde você normalmente utilizaria um objeto de único método.
Situações onde você poderia utilizar isto são comuns em ambientes web. Muitos códigos escritos em JavaScript para web são baseados em eventos - nós definimos algum comportamento e então, o atribuimos a um evento que será disparado pelo usuário (quando uma tecla for pressionada, por exemplo). Nosso código normalmente é utilizado como callback: uma função que será executada como resposta ao evento.
Aqui temos um exemplo prático: suponha que queremos adicionar alguns botões para ajustar o tamanho do texto de uma página. Um jeito de fazer seria especificar o tamanho da fonte no elemento body e então definir o tamanho dos outros elementos da página (os cabeçalhos, por exemplo) utilizando a unidade relativa em:
body { font-family: Helvetica, Arial, sans-serif; font-size: 12px;}h1 { font-size: 1.5em;}h2 { font-size: 1.2em;}Nossos botões interativos de tamanho de texto podem alterar a propriedade font-size do elemento body, e os ajustes serão refletidos em outros elementos graças à unidade relativa.
O código #"px"; };}var size12 = makeSizer(12);var size14 = makeSizer(14);var size16 = makeSizer(16);
size12,size14 esize16 agora são funções que devem redimensionar o texto do elemento body para 12, 14 e 16 pixels respectivamente. Nós podemos designá-las a botões (neste caso, links) como feito a seguir:
document.getElementById("size-12").onclick = size12;document.getElementById("size-14").onclick = size14;document.getElementById("size-16").onclick = size16;<a href="#">12</a><a href="#">14</a><a href="#">16</a>Emulando métodos privados com closures
Linguagens como Java oferecem a habilidade de declarar métodos privados, o que significa que eles só poderão ser chamados por outros métodos na mesma classe.
O JavaScript não oferece uma maneira nativa de fazer isso, mas é possível emular métodos privados usando closures. Métodos privados não são somente úteis para restringir acesso ao código: eles também oferecem uma maneira eficaz de gerenciar seu namespace global, evitando que métodos não essenciais baguncem a interface pública do seu código.
Veja como definir algumas funções públicas que acessam funções e variáveis privadas, usando closures que também é conhecido comomodule pattern:
var Counter = (function () { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function () { changeBy(1); }, decrement: function () { changeBy(-1); }, value: function () { return privateCounter; }, };})();alert(Counter.value()); /* Alerts 0 */Counter.increment();Counter.increment();alert(Counter.value()); /* Alerts 2 */Counter.decrement();alert(Counter.value()); /* Alerts 1 */Tem muita coisa acontecendo aqui. Nos exemplos anteriores cada closure teve o seu próprio ambiente; aqui nós criamos um ambiente único que é compartilhado por três funções:Counter.increment,Counter.decrement eCounter.value.
O ambiente compartilhado é criado no corpo de uma função anônima, da qual é executada assim que é definida. O ambiente contém dois itens privados: uma variável chamadaprivateCounter e uma função chamadachangeBy. Nenhum desses itens privados podem ser acessados diretamente de fora da função anônima. Ao invés disso, eles devem ser acessados pelas três funções públicas que são retornadas.
Aquelas três funções públicas são closures que compartilham o mesmo ambiente. Graças ao escopo léxico do JavaScript, cada uma delas tem acesso a variávelprivateCounter e à funçãochangeBy.
Nota:Você perceberá que estamos definindo uma função anônima que cria um contador , e então o executamos imediatamente e atribuímos o resultado à variávelCounter. Poderíamos armazenar essa função em uma variável separada e usá-la para criar diversos contadores.
var makeCounter = function () { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function () { changeBy(1); }, decrement: function () { changeBy(-1); }, value: function () { return privateCounter; }, };};var Counter1 = makeCounter();var Counter2 = makeCounter();alert(Counter1.value()); /* Alerts 0 */Counter1.increment();Counter1.increment();alert(Counter1.value()); /* Alerts 2 */Counter1.decrement();alert(Counter1.value()); /* Alerts 1 */alert(Counter2.value()); /* Alerts 0 */Observe como cada um dos contadores mantém a sua independência em relação ao outro. Seu ambiente durante a execução da funçãomakeCounter() é diferente a cada vez que ocorre. A variávelprivateCounter contém uma instância diferente a cada vez.
Nota:Usar closures desta maneira oferece uma série de benefícios que estão normalmente associados a programação orientada a objetos, em particular encapsulamento e ocultação de dados.
Criando closures dentro de loops: Um erro comum
Antes da introdução da palavra chavelet no JavaScript 1.7, um problema comum ocorria com closures quando eram criadas dentro de um loop. Considere o exemplo:
<p>Notas úteis aparecerão aqui</p><p>E-mail: <input type="text" name="email" /></p><p>Nome: <input type="text" name="name" /></p><p>Idade: <input type="text" name="age" /></p>function showHelp(help) { document.getElementById("help").innerHTML = help;}function setupHelp() { var helpText = [ { id: "email", help: "Seu e-mail" }, { id: "name", help: "Seu nome completo" }, { id: "age", help: "Sua idade (você deve ter mais de 16 anos)" }, ]; for (var i = 0; i < helpText.length; i++) { // O culpado é o uso do `var` nesta linha var item = helpText[i]; document.getElementById(item.id).onfocus = function () { showHelp(item.help); }; }}setupHelp();O arrayhelpText define três dicas úteis, cada uma associada ao ID de um input no documento. O loop percorre essas definições, atrelando um eventoonfocus para cada um que mostra o método de ajuda associado.
Se você tentar executar esse código, Você verá que não vai funcionar como esperado. Não importa em qual campo ocorre o focus, a mensagem sobre a sua idade será mostrada.
O motivo disto é que as funções atreladas aoonfocus são closures; elas consistem na definição da função e do ambiente capturado do escopo da funçãosetupHelp. Três closures foram criados, mas todos eles compartilham o mesmo ambiente. No momento em que os callbacks doonfocus são executados, o loop segue seu curso e então a variávelitem (compartilhada por todos os três closures) fica apontando para a última entrada na listahelpText.
Uma solução seria neste caso usar mais closures: em particular, usar uma fábrica de funções como descrito anteriormente:
function showHelp(help) { document.getElementById("help").textContent = help;}function makeHelpCallback(help) { return function () { showHelp(help); };}function setupHelp() { var helpText = [ { id: "email", help: "Seu e-mail" }, { id: "name", help: "Seu nome completo" }, { id: "age", help: "Sua idade (você deve ter mais de 16 anos)" }, ]; for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = makeHelpCallback(item.help); }}setupHelp();Isto funciona conforme o esperado. Ao invés dos callbacks compartilharem o mesmo ambiente, a funçãomakeHelpCallback cria um novo ambiente para cada um no qualhelp se refere à string correspondente do arrayhelpText.
Uma outra maneira de escrever o mesmo usando closures anônimas é:
function showHelp(help) { document.getElementById("help").textContent = help;}function setupHelp() { var helpText = [ { id: "email", help: "Seu e-mail" }, { id: "name", help: "Seu nome completo" }, { id: "age", help: "Sua idade (você deve ter mais de 16 anos)" }, ]; for (var i = 0; i < helpText.length; i++) { (function () { var item = helpText[i]; document.getElementById(item.id).onfocus = function () { showHelp(item.help); }; })(); // Expressão de função invocada imediatamente com o valor atual do item (preservado até a iteração). }}setupHelp();Se você não quiser usar mais closures, você pode usar a palavra-chavelet ouconst:
function showHelp(help) { document.getElementById("help").textContent = help;}function setupHelp() { var helpText = [ { id: "email", help: "Seu e-mail" }, { id: "name", help: "Seu nome completo" }, { id: "age", help: "Sua idade (você deve ter mais de 16 anos)" }, ]; for (let i = 0; i < helpText.length; i++) { const item = helpText[i]; document.getElementById(item.id).onfocus = () => { showHelp(item.help); }; }}setupHelp();Este exemplo usaconst em vez devar, portanto cada closure vincula a variável com escopo de bloco, o que significa que nenhuma closure adicional é necessária.
Outra alternativa poderia ser usarforEach() para iterar sobre o arrayhelpText e anexar um ouvinte a cada<input>, conforme mostrado:
function showHelp(help) { document.getElementById("help").textContent = help;}function setupHelp() { var helpText = [ { id: "email", help: "Seu e-mail" }, { id: "name", help: "Seu nome completo" }, { id: "age", help: "Sua idade (você deve ter mais de 16 anos)" }, ]; helpText.forEach(function (text) { document.getElementById(text.id).onfocus = function () { showHelp(text.help); }; });}setupHelp();Considerações de performance
Não é sábio criar funções dentro de outras funções se a closure não for necessário para uma tarefa em particular, pois ele afetará a performance do script de forma bem negativa tanto em velocidade de processamento quanto em consumo de memória.
Por exemplo, ao criar uma nova classe/objeto, os métodos devem normalmente estar associados ao protótipo do objeto do que definido no construtor. O motivo disso é que sempre que o construtor for chamado os métodos serão reatribuídos (isto é, para cada criação de objeto).
Considere o seguinte exemplo pouco prático porém demonstrativo:
function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); this.getName = function () { return this.name; }; this.getMessage = function () { return this.message; };}O código anterior não aproveita os benefícios dos closures e portanto poderia ser reformulado assim:
function MyObject(name, message) { this.name = name.toString(); this.message = message.toString();}MyObject.prototype = { getName: function () { return this.name; }, getMessage: function () { return this.message; },};Ou assim:
function MyObject(name, message) { this.name = name.toString(); this.message = message.toString();}MyObject.prototype.getName = function () { return this.name;};MyObject.prototype.getMessage = function () { return this.message;};Nos dois exemplos anteriores, o protótipo herdado pode ser compartilhado por todos os objetos, e as definições de métodos não precisam ocorrer sempre que o objeto for criado. VejaDetalhes do modelo de objeto para mais detalhes.