[ Web Proxy ]
URL:
Viewing: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Guide/Closures [Back]  [Original]

Closures - JavaScript | MDN

Esta pgina foi traduzida do ingls pela comunidade. Saiba mais e junte-se comunidade MDN Web Docs.

View in English Always switch to English

Closures

Uma closure a combinao de uma funo com as referncias ao estado que a circunda (o ambiente lxico). Em outras palavras, uma closure lhe d acesso ao escopo de uma funo externa a partir de uma funo interna. Em JavaScript, as closures so criadas toda vez que uma funo criada, no momento da criao da funo.

In this article

Escopo lxico

Considere a funo abaixo:

js
function init() {
  var name = "Mozilla"; // name  uma varivel local criada pelo init
  function displayName() {
    // displayName()  a funo interna, uma closure
    console.log(name); // usa a varivel declarada na funo pai
  }
  displayName();
}
init();

A funo init() cria uma varivel local chamada name, e depois define uma funo chamada displayName(). displayName() uma funo aninhada (uma closure) ela definida dentro da funo init(), e est disponivel apenas dentro do corpo daquela funo. Diferente de init(), displayName() no tem variveis locais prprias, e ao invs disso reusa a varivel name declarada na funo pai.

Rode o cdigo e veja que isso funciona. Este um exemplo de escopo lxico: em JavaScript, o escopo de uma varivel definido por sua localizao dentro do cdigo fonte (isto aparentemente lxico) e funes aninhadas tm acesso s variveis declaradas em seu escopo externo.

Closure

Agora considere o seguinte exemplo:

js
function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

Se voc rodar este cdigo o mesmo ter exatamente o mesmo efeito que o init() do exemplo anterior: a palavra "Mozilla" ser mostrada na caixa de alerta. O que diferente - e interessante - o fato de que a funo interna do displayName() foi retornada da funo externa antes de ser executada.

Pode parecer no muito intuitivo de que o cdigo de fato funciona. Normalmente variveis locais de uma funo, apenas existem pela durao de sua execuo. Uma vez que makeFunc() terminou de executar, razovel esperar que a varivel name no ser mais necessria. Dado que o cdigo ainda funciona como o esperado, este no o caso.

A soluo para tal problema que a funo myFunc tornou-se uma closure. Uma closure trata-se de um tipo especial de objeto que combina duas coisas: a funo e o ambiente onde a funo foi criada. Este ambiente consiste de quaisquer variveis que estavam no escopo naquele momento em que a funo foi criada. Neste caso, myFunc a closure que incorpora tanto a funo displayName quanto a palavra Mozilla que existia quando a closure foi criada.

Aqui temos um exemplo um pouco mais interessante, a funo makeAdder:

js
function makeAdder(x) {
  return function (y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

print(add5(2)); // 7
print(add10(2)); // 12

Neste exemplo definimos a funo makeAdder(x) que toma um nico argumento x e retorna uma nova funo. A funo retornada toma ento um nico argumento, y, e retorna ento a soma de x e de y.

Na essncia o makeAdder trata-se de uma funo fbrica - ir construir outras funes que podem adicionar um determinado valor especfico a seu argumento. No exemplo acima usamos a fbrica de funes para criar duas novas funes - uma que adiciona 5 ao argumento, e outra que adiciona 10.

Ambas as funes add5 e add10 so closures. Compartilham o mesmo corpo de definio de funo mas armazenam diferentes ambientes. No ambiente da add5, por exemplo, x equivale a 5, enquanto na add10 o valor de x 10.

Closures na prtica

Esta a teoria mas closures so realmente teis? Vamos considerar suas aplicaes prticas. Uma closure deixa voc associar dados (do ambiente) com uma funo que trabalha estes dados. Isto est diretamente ligado com programao orientada a objetos, onde objetos nos permitem associar dados (as propriedades do objeto) utilizando um ou mais mtodos.

Consequentemente, voc pode utilizar uma closure em qualquer lugar onde voc normalmente utilizaria um objeto de nico mtodo.

Situaes onde voc poderia utilizar isto so comuns em ambientes web. Muitos cdigos escritos em JavaScript para web so baseados em eventos - ns definimos algum comportamento e ento, o atribuimos a um evento que ser disparado pelo usurio (quando uma tecla for pressionada, por exemplo). Nosso cdigo normalmente utilizado como callback: uma funo que ser executada como resposta ao evento.

Aqui temos um exemplo prtico: suponha que queremos adicionar alguns botes para ajustar o tamanho do texto de uma pgina. Um jeito de fazer seria especificar o tamanho da fonte no elemento body e ento definir o tamanho dos outros elementos da pgina (os cabealhos, por exemplo) utilizando a unidade relativa em:

css
body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}
h2 {
  font-size: 1.2em;
}

Nossos botes interativos de tamanho de texto podem alterar a propriedade font-size do elemento body, e os ajustes sero refletidos em outros elementos graas unidade relativa.

O cdigo JavaScript:

js
function makeSizer(size) {
  return function () {
    document.body.style.fontSize = size + "px";
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

size12, size14 e size16 agora so funes que devem redimensionar o texto do elemento body para 12, 14 e 16 pixels respectivamente. Ns podemos design-las a botes (neste caso, links) como feito a seguir:

js
document.getElementById("size-12").onclick = size12;
document.getElementById("size-14").onclick = size14;
document.getElementById("size-16").onclick = size16;
html
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>

View on JSFiddle

Emulando mtodos privados com closures

Linguagens como Java oferecem a habilidade de declarar mtodos privados, o que significa que eles s podero ser chamados por outros mtodos na mesma classe.

O JavaScript no oferece uma maneira nativa de fazer isso, mas possvel emular mtodos privados usando closures. Mtodos privados no so somente teis para restringir acesso ao cdigo: eles tambm oferecem uma maneira eficaz de gerenciar seu namespace global, evitando que mtodos no essenciais baguncem a interface pblica do seu cdigo.

Veja como definir algumas funes pblicas que acessam funes e variveis privadas, usando closures que tambm conhecido como module pattern:

js
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 prprio ambiente; aqui ns criamos um ambiente nico que compartilhado por trs funes: Counter.increment, Counter.decrement e Counter.value.

O ambiente compartilhado criado no corpo de uma funo annima, da qual executada assim que definida. O ambiente contm dois itens privados: uma varivel chamada privateCounter e uma funo chamada changeBy. Nenhum desses itens privados podem ser acessados diretamente de fora da funo annima. Ao invs disso, eles devem ser acessados pelas trs funes pblicas que so retornadas.

Aquelas trs funes pblicas so closures que compartilham o mesmo ambiente. Graas ao escopo lxico do JavaScript, cada uma delas tem acesso a varivel privateCounter e funo changeBy.

Nota: Voc perceber que estamos definindo uma funo annima que cria um contador , e ento o executamos imediatamente e atribumos o resultado varivel Counter. Poderamos armazenar essa funo em uma varivel separada e us-la para criar diversos contadores.

js
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 mantm a sua independncia em relao ao outro. Seu ambiente durante a execuo da funo makeCounter() diferente a cada vez que ocorre. A varivel privateCounter contm uma instncia diferente a cada vez.

Nota: Usar closures desta maneira oferece uma srie de benefcios que esto normalmente associados a programao orientada a objetos, em particular encapsulamento e ocultao de dados.

Criando closures dentro de loops: Um erro comum

Antes da introduo da palavra chave let no JavaScript 1.7, um problema comum ocorria com closures quando eram criadas dentro de um loop. Considere o exemplo:

html
<p id="help">Notas teis aparecero aqui</p>
<p>E-mail: <input type="text" id="email" name="email" /></p>
<p>Nome: <input type="text" id="name" name="name" /></p>
<p>Idade: <input type="text" id="age" name="age" /></p>
js
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();

View on JSFiddle

O array helpText define trs dicas teis, cada uma associada ao ID de um input no documento. O loop percorre essas definies, atrelando um evento onfocus para cada um que mostra o mtodo de ajuda associado.

Se voc tentar executar esse cdigo, Voc ver que no vai funcionar como esperado. No importa em qual campo ocorre o focus, a mensagem sobre a sua idade ser mostrada.

O motivo disto que as funes atreladas ao onfocus so closures; elas consistem na definio da funo e do ambiente capturado do escopo da funo setupHelp. Trs closures foram criados, mas todos eles compartilham o mesmo ambiente. No momento em que os callbacks do onfocus so executados, o loop segue seu curso e ento a varivel item (compartilhada por todos os trs closures) fica apontando para a ltima entrada na lista helpText.

Uma soluo seria neste caso usar mais closures: em particular, usar uma fbrica de funes como descrito anteriormente:

js
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();

View on JSFiddle

Isto funciona conforme o esperado. Ao invs dos callbacks compartilharem o mesmo ambiente, a funo makeHelpCallback cria um novo ambiente para cada um no qual help se refere string correspondente do array helpText.

Uma outra maneira de escrever o mesmo usando closures annimas :

js
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);
      };
    })(); // Expresso de funo invocada imediatamente com o valor atual do item (preservado at a iterao).
  }
}

setupHelp();

Se voc no quiser usar mais closures, voc pode usar a palavra-chave let ou const:

js
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 usa const em vez de var, portanto cada closure vincula a varivel com escopo de bloco, o que significa que nenhuma closure adicional necessria.

Outra alternativa poderia ser usar forEach() para iterar sobre o array helpText e anexar um ouvinte a cada <input>, conforme mostrado:

js
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();

Consideraes de performance

No sbio criar funes dentro de outras funes se a closure no for necessrio 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 memria.

Por exemplo, ao criar uma nova classe/objeto, os mtodos devem normalmente estar associados ao prottipo do objeto do que definido no construtor. O motivo disso que sempre que o construtor for chamado os mtodos sero reatribudos (isto , para cada criao de objeto).

Considere o seguinte exemplo pouco prtico porm demonstrativo:

js
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 cdigo anterior no aproveita os benefcios dos closures e portanto poderia ser reformulado assim:

js
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:

js
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 prottipo herdado pode ser compartilhado por todos os objetos, e as definies de mtodos no precisam ocorrer sempre que o objeto for criado. Veja Detalhes do modelo de objeto para mais detalhes.


Web Proxy Viewer  |  New URL  |  Original Page