1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
|
---
title: Closures
slug: Web/JavaScript/Closures
tags:
- Closure
- ES5
- Intermediário
- JavaScript
- Referencia
translation_of: Web/JavaScript/Closures
original_slug: Web/JavaScript/Guide/Closures
---
<div>{{jsSidebar("Intermediate")}}</div>
<p class="summary">Um <em>closure </em>(fechamento) é uma função que se "lembra" do ambiente — ou escopo léxico — em que ela foi criada.</p>
<h2 id="Escopo_léxico">Escopo léxico</h2>
<p>Considere a função abaixo:</p>
<div style="width: auto; overflow: hidden;">
<pre class="brush: js">function init() {
var name = "Mozilla";
function displayName() {
alert(name);
}
displayName();
}
init();
</pre>
</div>
<p>A função <code>init()</code> cria uma variável local chamada <code>name</code>, e depois define uma função chamada <code>displayName()</code>. <code>displayName()</code> é uma função aninhada (um <em>closure</em>) — ela é definida dentro da função <code>init()</code>, e está disponivel apenas dentro do corpo daquela função. Diferente de <span style="font-family: courier new,andale mono,monospace; line-height: 1.5;">init()</span><span style="line-height: 1.5;">,</span><span style="line-height: 1.5;"> </span><code style="font-style: normal; line-height: 1.5;">displayName()</code><span style="line-height: 1.5;"> não tem variáveis locais próprias, e ao invés disso reusa a variável </span><code style="font-style: normal; line-height: 1.5;">name</code><span style="line-height: 1.5;"> declarada na função pai.</span></p>
<p><a href="http://jsfiddle.net/xAFs9/3/">Rode</a> o código e veja que isso funciona. Este é um exemplo de <em>escopo léxico:</em> em JavaScript, o escopo de uma variável é definido por sua localização dentro do código fonte (isto é aparentemente <em>léxico</em>) e funções aninhadas têm acesso às variáveis declaradas em seu escopo externo.</p>
<h2 id="Closure">Closure</h2>
<p>Agora considere o seguinte exemplo:</p>
<pre class="brush: js">function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
</pre>
<p>Se você rodar este código o mesmo terá exatamente o mesmo efeito que o <code>init()</code> 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 do <code>displayName()</code> foi retornada da função externa antes de ser executada.</p>
<p>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 que <code>makeFunc()</code> terminou de executar, é razoável esperar que a variável <code>name</code> não será mais necessária. Dado que o código ainda funciona como o esperado, este não é o caso.</p>
<p>A solução para tal problema é que a função <code>myFunc</code> tornou-se uma <code>closure</code>. Uma closure (fechamento) 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, <code>myFunc</code> é a closure que incorpora tanto a função <code>displayName</code> quanto a palavra <em>Mozilla</em> que existia quando a closure foi criada.</p>
<p>Aqui temos um exemplo um pouco mais interessante, a função <code>makeAdder</code>:</p>
<pre class="brush: 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
</pre>
<p>Neste exemplo definimos a função <code>makeAdder(x)</code> que toma um único argumento <code>x</code> e retorna uma nova função. A função retornada toma então um único argumento, <code>y</code>, e retorna então a soma de <code>x</code> e de <code>y</code>.</p>
<p>Na essência o <code>makeAdder</code> trata-se de uma <em>função fábrica - </em>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.</p>
<p>Ambas as funções <code>add5</code> e <code>add10</code><em> </em><code> </code>são closures. Compartilham o mesmo corpo de definição de função mas armazenam diferentes ambientes. No ambiente da <code>add5</code>, por exemplo, <code>x</code> equivale a 5, enquanto na <code>add10</code> o valor de x é 10.</p>
<h2 id="Closures_na_prática">Closures na prática</h2>
<p>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.</p>
<p>Consequentemente, você pode utilizar uma closure em qualquer lugar onde você normalmente utilizaria um objeto de único método.</p>
<p>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.</p>
<p>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:</p>
<pre class="brush: css">body {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.2em;
}
</pre>
<p>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.</p>
<p>O código JavaScript:</p>
<pre class="brush: js">function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
</pre>
<p><code>size12</code>, <code>size14</code> e <code>size16</code> 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:</p>
<pre class="brush: js">document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
</pre>
<pre class="brush: html"><a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>
</pre>
<p><a href="https://jsfiddle.net/vnkuZ">View on JSFiddle</a></p>
<h2 id="Emulando_métodos_privados_com_closures">Emulando métodos privados com closures</h2>
<p>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.</p>
<p>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.</p>
<p>Veja como definir algumas funções públicas que acessam funções e variáveis privadas, usando closures que também é conhecido como <a class="external" href="http://www.google.com/search?q=javascript+module+pattern">module pattern</a>:</p>
<pre class="brush: 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 */
</pre>
<p>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: <code>Counter.increment</code>, <code>Counter.decrement</code> e <code>Counter.value</code>.</p>
<p>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 chamada <code>privateCounter</code> e uma função chamada <code>changeBy</code>. 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.</p>
<p>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ável <code>privateCounter</code> e à função <code>changeBy</code>.</p>
<div class="blockIndicator note">
<p>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ável <code>Counter</code>. Poderíamos armazenar essa função em uma variável separada e usá-la para criar diversos contadores.</p>
</div>
<pre class="brush: 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 */
</pre>
<p>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ção <code>makeCounter()</code> é diferente a cada vez que ocorre. A variável <code>privateCounter</code> contém uma instância diferente a cada vez.</p>
<div class="blockIndicator note">
<p>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.</p>
</div>
<dl>
</dl>
<h2 id="Criando_closures_dentro_de_loops_Um_erro_comum">Criando closures dentro de loops: Um erro comum</h2>
<p>Antes da introdução da palavra chave <a href="/en-US/docs/JavaScript/Reference/Statements/let" title="let"><code>let</code></a> no JavaScript 1.7, um problema comum ocorria com closures quando eram criadas dentro de um loop. Considere o exemplo:</p>
<pre class="brush: html"><p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>
</pre>
<pre class="brush: js">function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
}
}
setupHelp();
</pre>
<p><a href="https://jsfiddle.net/v7gjv">View on JSFiddle</a></p>
<p>O array <code>helpText</code> define três dicas úteis, cada uma associada ao ID de um input no documento. O loop percorre essas definições, atrelando um evento onfocus para cada um que mostra o método de ajuda associado.</p>
<p>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.</p>
<p>O motivo disto é que as funções atreladas ao onfocus são closures; elas consistem na definição da função e do ambiente capturado do escopo da função <code>setupHelp</code>. Três closures foram criados, mas todos eles compartilham o mesmo ambiente. No momento em que os callbacks do onfocus são executados, o loop segue seu curso e então a variável item (compartilhada por todos os três closures) fica apontando para a última entrada na lista <code>helpText</code>.</p>
<p>Uma solução seria neste caso usar mais closures: em particular, usar uma fábrica de funções como descrito anteriormente:</p>
<pre class="brush: js">function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function makeHelpCallback(help) {
return function() {
showHelp(help);
};
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
}
}
setupHelp();
</pre>
<p><a href="https://jsfiddle.net/v7gjv/1">View on JSFiddle</a></p>
<p>Isto funciona conforme o esperado. Ao invés dos callbacks compartilharem o mesmo ambiente, a função <code>makeHelpCallback</code> cria um novo ambiente para cada um no qual <code>help</code> se refere à string correspondente do array <code>helpText</code>.</p>
<h2 id="Considerações_de_performance">Considerações de performance</h2>
<p>Não é sábio criar funções dentro de outras funções se o 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.</p>
<p>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).</p>
<p>Considere o seguinte exemplo pouco prático porém demonstrativo:</p>
<pre class="brush: 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;
};
}
</pre>
<p>O código anterior não aproveita os benefícios dos closures e portanto poderia ser reformulado assim:</p>
<pre class="brush: 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;
}
};
</pre>
<p>Ou assim:</p>
<pre class="brush: 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;
};
</pre>
<p>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. Veja <a href="/en-US/docs/JavaScript/Guide/Details_of_the_Object_Model" title="Detalhes do modelo de objeto">Detalhes do modelo de objeto</a> para mais detalhes.</p>
|