JavaScript é uma linguagem muito orientada a funções. Isso nos dá muita liberdade. Uma função pode ser criada a qualquer momento, passada como argumento para outra função e, posteriormente, chamada de um local de código totalmente diferente.
Já sabemos que uma função pode acessar variáveis fora dela (variáveis “externas”).
Mas o que acontece se as variáveis externas mudarem desde que uma função é criada? A função obterá valores mais novos ou antigos?
E se uma função for passada como argumento e chamada de outro local do código, ela terá acesso a variáveis externas no novo local?
Vamos ampliar nosso conhecimento para entender esses cenários e outros mais complexos.
Falaremos sobre variáveis let/const
aqui
Em JavaScript, existem 3 maneiras de declarar uma variável: let
, const
(as modernas) e var
(o remanescente do passado).
Neste artigo usaremos variáveis let
em exemplos.
Variáveis, declaradas com const
, se comportam da mesma forma, então este artigo também trata de const
.
O antigo var
tem algumas diferenças notáveis, que serão abordadas no artigo O antigo "var".
Se uma variável for declarada dentro de um bloco de código {...}
, ela só será visível dentro desse bloco.
Por exemplo:
{ //faça algum trabalho com variáveis locais que não deveriam ser vistas fora deixe mensagem = "Olá"; //visível apenas neste bloco alerta(mensagem); // Olá } alerta(mensagem); // Erro: a mensagem não está definida
Podemos usar isso para isolar um trecho de código que executa sua própria tarefa, com variáveis que pertencem apenas a ele:
{ //mostra mensagem deixe mensagem = "Olá"; alerta(mensagem); } { //mostra outra mensagem deixe mensagem = "Adeus"; alerta(mensagem); }
Haveria um erro sem bloqueios
Observe que sem blocos separados haveria um erro se usarmos let
com o nome da variável existente:
//mostra mensagem deixe mensagem = "Olá"; alerta(mensagem); //mostra outra mensagem deixe mensagem = "Adeus"; //Erro: variável já declarada alerta(mensagem);
Pois if
, for
, while
e assim por diante, variáveis declaradas em {...}
também só são visíveis dentro de:
se (verdadeiro) { deixe frase = "Olá!"; alerta(frase); // Olá! } alerta(frase); // Erro, tal variável não existe!
Aqui, após if
terminar, o alert
abaixo não verá a phrase
, daí o erro.
Isso é ótimo, pois nos permite criar variáveis locais de bloco, específicas para uma ramificação if
.
O mesmo vale para os loops for
e while
:
for (seja i = 0; i < 3; i++) { // a variável i só é visível dentro disto para alerta(eu); // 0, depois 1, depois 2 } alerta(eu); // Erro, essa variável não existe
Visualmente, let i
estar fora de {...}
. Mas a construção for
é especial aqui: a variável declarada dentro dela é considerada parte do bloco.
Uma função é chamada de “aninhada” quando é criada dentro de outra função.
É facilmente possível fazer isso com JavaScript.
Podemos usá-lo para organizar nosso código, assim:
function digaOiTchau(primeiroNome, sobrenome) { // função aninhada auxiliar para usar abaixo function getNomeCompleto() { return nome + " " + sobrenome; } alert("Olá," + getFullName() ); alert("Tchau," + getFullName() ); }
Aqui, a função aninhada getFullName()
é criada por conveniência. Ele pode acessar as variáveis externas e, portanto, retornar o nome completo. Funções aninhadas são bastante comuns em JavaScript.
O que é muito mais interessante é que uma função aninhada pode ser retornada: como propriedade de um novo objeto ou como resultado por si só. Ele pode então ser usado em outro lugar. Não importa onde, ele ainda terá acesso às mesmas variáveis externas.
Abaixo, makeCounter
cria a função “counter” que retorna o próximo número em cada invocação:
function makeContador() { deixe contar = 0; função de retorno() { retornar contagem++; }; } deixe contador = makeCounter(); alerta(contador()); //0 alerta(contador()); //1 alerta(contador()); //2
Apesar de simples, variantes ligeiramente modificadas desse código têm usos práticos, por exemplo, como gerador de números aleatórios para gerar valores aleatórios para testes automatizados.
Como é que isso funciona? Se criarmos vários contadores, eles serão independentes? O que está acontecendo com as variáveis aqui?
Compreender essas coisas é ótimo para o conhecimento geral de JavaScript e benéfico para cenários mais complexos. Então, vamos nos aprofundar um pouco.
Aqui estão dragões!
A explicação técnica aprofundada está à frente.
Na medida em que eu gostaria de evitar detalhes de linguagem de baixo nível, qualquer compreensão sem eles seria insuficiente e incompleta, então prepare-se.
Para maior clareza, a explicação é dividida em várias etapas.
Em JavaScript, cada função em execução, bloco de código {...}
e o script como um todo possuem um objeto interno (oculto) associado conhecido como Ambiente Lexical .
O objeto Lexical Environment consiste em duas partes:
Registro de Ambiente – um objeto que armazena todas as variáveis locais como suas propriedades (e algumas outras informações como o valor this
).
Uma referência ao ambiente lexical externo , aquele associado ao código externo.
Uma “variável” é apenas uma propriedade do objeto interno especial, Environment Record
. “Obter ou alterar uma variável” significa “obter ou alterar uma propriedade desse objeto”.
Neste código simples sem funções, existe apenas um Ambiente Lexical:
Este é o chamado Ambiente Lexical global , associado a todo o script.
Na figura acima, o retângulo significa Registro de Ambiente (armazenamento de variáveis) e a seta significa a referência externa. O Ambiente Lexical global não possui referência externa, por isso a seta aponta para null
.
À medida que o código começa a ser executado e continua, o ambiente lexical muda.
Aqui está um código um pouco mais longo:
Os retângulos do lado direito demonstram como o Ambiente Lexical global muda durante a execução:
Quando o script é iniciado, o Ambiente Lexical é pré-preenchido com todas as variáveis declaradas.
Inicialmente, eles estão no estado “Não inicializado”. Esse é um estado interno especial, significa que o mecanismo conhece a variável, mas não pode ser referenciado até que seja declarado com let
. É quase o mesmo que se a variável não existisse.
Então let phrase
aparecer. Ainda não há atribuição, então seu valor é undefined
. Podemos usar a variável deste ponto em diante.
phrase
recebe um valor.
phrase
altera o valor.
Tudo parece simples por enquanto, certo?
Uma variável é uma propriedade de um objeto interno especial, associado ao bloco/função/script atualmente em execução.
Trabalhar com variáveis é na verdade trabalhar com as propriedades desse objeto.
Ambiente Lexical é um objeto de especificação
“Ambiente Lexical” é um objeto de especificação: ele só existe “teoricamente” na especificação da linguagem para descrever como as coisas funcionam. Não podemos colocar esse objeto em nosso código e manipulá-lo diretamente.
Os mecanismos JavaScript também podem otimizá-lo, descartar variáveis que não são utilizadas para economizar memória e realizar outros truques internos, desde que o comportamento visível permaneça conforme descrito.
Uma função também é um valor, como uma variável.
A diferença é que uma declaração de função é totalmente inicializada instantaneamente.
Quando um Ambiente Lexical é criado, uma Declaração de Função torna-se imediatamente uma função pronta para uso (ao contrário de let
, que fica inutilizável até a declaração).
É por isso que podemos usar uma função, declarada como Declaração de Função, antes mesmo da declaração em si.
Por exemplo, aqui está o estado inicial do Ambiente Lexical global quando adicionamos uma função:
Naturalmente, esse comportamento se aplica apenas a declarações de função, não a expressões de função onde atribuímos uma função a uma variável, como let say = function(name)...
.
Quando uma função é executada, no início da chamada, um novo Ambiente Lexical é criado automaticamente para armazenar variáveis e parâmetros locais da chamada.
Por exemplo, para say("John")
, fica assim (a execução é na linha, marcada com uma seta):
Durante a chamada de função temos dois Ambientes Lexicais: o interno (para a chamada de função) e o externo (global):
O Ambiente Lexical interno corresponde à execução atual de say
. Possui uma única propriedade: name
, o argumento da função. Chamamos say("John")
, então o valor do name
é "John"
.
O Ambiente Lexical externo é o Ambiente Lexical global. Possui a variável phrase
e a própria função.
O Ambiente Lexical interno tem uma referência ao outer
.
Quando o código deseja acessar uma variável – busca-se primeiro o Ambiente Lexical interno, depois o externo, depois o mais externo e assim sucessivamente até o global.
Se uma variável não for encontrada em nenhum lugar, isso é um erro no modo estrito (sem use strict
, uma atribuição a uma variável inexistente cria uma nova variável global, para compatibilidade com o código antigo).
Neste exemplo a pesquisa prossegue da seguinte forma:
Para a variável name
, o alert
dentro say
a encontra imediatamente no Ambiente Lexical interno.
Quando ele deseja acessar phrase
, então não há nenhuma phrase
localmente, então ele segue a referência ao Ambiente Lexical externo e a encontra lá.
Voltemos ao exemplo makeCounter
.
function makeContador() { deixe contar = 0; função de retorno() { contagem de retorno++; }; } deixe contador = makeCounter();
No início de cada chamada makeCounter()
, um novo objeto Lexical Environment é criado, para armazenar variáveis para esta execução makeCounter
.
Portanto temos dois Ambientes Lexicais aninhados, assim como no exemplo acima:
A diferença é que, durante a execução de makeCounter()
, uma pequena função aninhada é criada de apenas uma linha: return count++
. Ainda não executamos, apenas criamos.
Todas as funções lembram o Ambiente Lexical em que foram feitas. Tecnicamente, não há mágica aqui: todas as funções possuem a propriedade oculta chamada [[Environment]]
, que mantém a referência ao Ambiente Lexical onde a função foi criada:
Portanto, counter.[[Environment]]
tem a referência a {count: 0}
Lexical Environment. É assim que a função lembra onde foi criada, não importa onde seja chamada. A referência [[Environment]]
é definida uma vez e para sempre no momento da criação da função.
Mais tarde, quando counter()
é chamado, um novo Ambiente Lexical é criado para a chamada, e sua referência externa ao Ambiente Lexical é obtida de counter.[[Environment]]
:
Agora, quando o código dentro de counter()
procura pela variável count
, ele primeiro procura seu próprio Ambiente Lexical (vazio, pois não há variáveis locais lá), depois o Ambiente Lexical da chamada makeCounter()
externa, onde a encontra e altera. .
Uma variável é atualizada no Ambiente Lexical onde ela reside.
Aqui está o estado após a execução:
Se chamarmos counter()
várias vezes, a variável count
será aumentada para 2
, 3
e assim por diante, no mesmo local.
Encerramento
Existe um termo geral de programação “fechamento”, que os desenvolvedores geralmente devem conhecer.
Um encerramento é uma função que lembra suas variáveis externas e pode acessá-las. Em algumas linguagens isso não é possível, ou uma função deve ser escrita de uma maneira especial para que isso aconteça. Mas, como explicado acima, em JavaScript, todas as funções são naturalmente fechamentos (há apenas uma exceção, a ser abordada na sintaxe da "nova função").
Ou seja: eles lembram automaticamente onde foram criados usando uma propriedade oculta [[Environment]]
e então seu código pode acessar variáveis externas.
Quando em uma entrevista, um desenvolvedor frontend recebe uma pergunta sobre “o que é um encerramento?”, uma resposta válida seria uma definição do encerramento e uma explicação de que todas as funções em JavaScript são encerramentos, e talvez mais algumas palavras sobre detalhes técnicos: a propriedade [[Environment]]
e como funcionam os ambientes lexicais.
Normalmente, um Ambiente Lexical é removido da memória com todas as variáveis após o término da chamada da função. Isso porque não há referências a isso. Como qualquer objeto JavaScript, ele só é mantido na memória enquanto estiver acessível.
No entanto, se houver uma função aninhada que ainda possa ser acessada após o final de uma função, ela terá a propriedade [[Environment]]
que faz referência ao ambiente léxico.
Nesse caso, o Ambiente Lexical ainda pode ser acessado mesmo após a conclusão da função, portanto permanece ativo.
Por exemplo:
função f() { deixe valor = 123; função de retorno() { alerta(valor); } } seja g = f(); // g.[[Environment]] armazena uma referência ao Ambiente Lexical // da chamada f() correspondente
Observe que se f()
for chamado muitas vezes e as funções resultantes forem salvas, todos os objetos do Ambiente Lexical correspondentes também serão retidos na memória. No código abaixo, todos os 3:
função f() { deixe valor = Math.random(); return function() { alerta(valor); }; } // 3 funções no array, cada uma delas vinculada ao Ambiente Lexical // da execução f() correspondente deixe arr = [f(), f(), f()];
Um objeto Lexical Environment morre quando se torna inacessível (assim como qualquer outro objeto). Em outras palavras, ele existe apenas enquanto houver pelo menos uma função aninhada fazendo referência a ele.
No código abaixo, depois que a função aninhada é removida, seu ambiente léxico envolvente (e, portanto, o value
) é limpo da memória:
função f() { deixe valor = 123; função de retorno() { alerta(valor); } } seja g = f(); // enquanto a função g existir, o valor permanece na memória g = nulo; // ...e agora a memória está limpa
Como vimos, em teoria, enquanto uma função está ativa, todas as variáveis externas também são retidas.
Mas, na prática, os motores JavaScript tentam otimizar isso. Eles analisam o uso de variáveis e se for óbvio no código que uma variável externa não é usada – ela é removida.
Um efeito colateral importante no V8 (Chrome, Edge, Opera) é que tal variável ficará indisponível na depuração.
Tente executar o exemplo abaixo no Chrome com as Ferramentas do desenvolvedor abertas.
Quando ele pausar, no console digite alert(value)
.
função f() { deixe valor = Math.random(); função g() { depurador; // no console: digite alerta(valor); Essa variável não existe! } retornar g; } seja g = f(); g();
Como você pode ver – essa variável não existe! Em teoria, deveria ser acessível, mas o motor o otimizou.
Isso pode levar a problemas de depuração engraçados (se não tão demorados). Um deles – podemos ver uma variável externa com o mesmo nome em vez da esperada:
deixe valor = "Surpresa!"; função f() { deixe valor = "o valor mais próximo"; função g() { depurador; // no console: digite alerta(valor); Surpresa! } retornar g; } seja g = f(); g();
É bom saber esse recurso do V8. Se você estiver depurando com Chrome/Edge/Opera, mais cedo ou mais tarde você o encontrará.
Isso não é um bug no depurador, mas sim um recurso especial do V8. Talvez isso seja mudado algum dia. Você sempre pode verificar isso executando os exemplos nesta página.
importância: 5
A função sayHi usa um nome de variável externa. Quando a função for executada, qual valor ela usará?
deixe nome = "João"; function digaOi() { alerta("Olá, " + nome); } nome = "Pedro"; digaOi(); // o que irá mostrar: "John" ou "Pete"?
Tais situações são comuns tanto no desenvolvimento do navegador quanto no desenvolvimento do lado do servidor. Uma função pode ser programada para ser executada mais tarde do que foi criada, por exemplo, após uma ação do usuário ou uma solicitação de rede.
Então, a questão é: ele capta as mudanças mais recentes?
A resposta é: Pete .
Uma função obtém variáveis externas como estão agora, ela usa os valores mais recentes.
Valores de variáveis antigos não são salvos em lugar nenhum. Quando uma função deseja uma variável, ela pega o valor atual de seu próprio ambiente lexical ou externo.
importância: 5
A função makeWorker
abaixo cria outra função e a retorna. Essa nova função pode ser chamada de outro lugar.
Ele terá acesso às variáveis externas do local de criação, do local de invocação ou de ambos?
função makeWorker() { deixe nome = "Pete"; função de retorno() { alerta(nome); }; } deixe nome = "João"; //cria uma função deixe trabalhar = makeWorker(); //chama trabalhar(); // o que isso mostrará?
Qual valor ele mostrará? “Pete” ou “João”?
A resposta é: Pete .
A função work()
no código abaixo recebe name
do local de sua origem por meio da referência do ambiente lexical externo:
Então, o resultado é "Pete"
aqui.
Mas se não houvesse let name
em makeWorker()
, então a pesquisa iria para fora e pegaria a variável global como podemos ver na cadeia acima. Nesse caso, o resultado seria "John"
.
importância: 5
Aqui fazemos dois contadores: counter
e counter2
usando a mesma função makeCounter
.
Eles são independentes? O que o segundo contador vai mostrar? 0,1
ou 2,3
ou algo mais?
function makeContador() { deixe contar = 0; função de retorno() { contagem de retorno++; }; } deixe contador = makeCounter(); deixe contador2 = makeCounter(); alerta(contador()); //0 alerta(contador()); //1 alerta(contador2()); // ? alerta(contador2()); // ?
A resposta: 0,1.
As funções counter
e counter2
são criadas por diferentes invocações de makeCounter
.
Portanto, eles possuem ambientes lexicais externos independentes, cada um com sua count
.
importância: 5
Aqui, um objeto contador é criado com a ajuda da função construtora.
Isso funcionará? O que isso vai mostrar?
função Contador() { deixe contar = 0; isto.up = função() { retornar ++contar; }; isto.down = function() { retornar --contar; }; } deixe contador = new Contador(); alerta(contador.up()); // ? alerta(contador.up()); // ? alerta(contador.down()); // ?
Certamente funcionará muito bem.
Ambas as funções aninhadas são criadas no mesmo ambiente lexical externo, portanto, compartilham acesso à mesma variável count
:
função Contador() { deixe contar = 0; isto.up = função() { retornar ++contar; }; isto.down = function() { retornar --contar; }; } deixe contador = new Contador(); alerta(contador.up()); //1 alerta(contador.up()); //2 alerta(contador.down()); //1
importância: 5
Veja o código. Qual será o resultado da chamada na última linha?
deixe frase = "Olá"; se (verdadeiro) { deixe usuário = "João"; function digaOi() { alerta(`${frase}, ${usuário}`); } } digaOi();
O resultado é um erro .
A função sayHi
é declarada dentro do if
, portanto só reside dentro dele. Não há sayHi
lá fora.
importância: 4
Escreva a função sum
que funciona assim: sum(a)(b) = a+b
.
Sim, exatamente desta forma, usando parênteses duplos (não é um erro de digitação).
Por exemplo:
soma(1)(2) = 3 soma(5)(-1) = 4
Para que os segundos parênteses funcionem, os primeiros devem retornar uma função.
Assim:
função soma(a) { função de retorno (b) { retornar a + b; // pega "a" do ambiente lexical externo }; } alerta(soma(1)(2) ); //3 alerta(soma(5)(-1) ); //4
importância: 4
Qual será o resultado deste código?
seja x = 1; função função() { console.log(x); // ? seja x = 2; } função();
PS Há uma armadilha nesta tarefa. A solução não é óbvia.
O resultado é: erro .
Tente executá-lo:
seja x = 1; função função() { console.log(x); // ReferenceError: Não é possível acessar 'x' antes da inicialização seja x = 2; } função();
Neste exemplo podemos observar a diferença peculiar entre uma variável “não existente” e uma “não inicializada”.
Como você deve ter lido no artigo Escopo de variável, fechamento, uma variável inicia no estado “não inicializado” a partir do momento em que a execução entra em um bloco de código (ou função). E permanece não inicializado até a instrução let
correspondente.
Em outras palavras, uma variável existe tecnicamente, mas não pode ser usada antes de let
.
O código acima demonstra isso.
função função() { // a variável local x é conhecida pelo mecanismo desde o início da função, // mas "não inicializado" (inutilizável) até let ("zona morta") // daí o erro console.log(x); // ReferenceError: Não é possível acessar 'x' antes da inicialização seja x = 2; }
Esta zona de inutilização temporária de uma variável (desde o início do bloco de código até let
) é às vezes chamada de “zona morta”.
importância: 5
Temos um método integrado arr.filter(f)
para arrays. Ele filtra todos os elementos através da função f
. Se retornar true
, esse elemento será retornado na matriz resultante.
Faça um conjunto de filtros “prontos para usar”:
inBetween(a, b)
– entre a
e b
ou igual a eles (inclusive).
inArray([...])
– no array fornecido.
O uso deve ser assim:
arr.filter(inBetween(3,6))
– seleciona apenas valores entre 3 e 6.
arr.filter(inArray([1,2,3]))
– seleciona apenas os elementos que correspondem a um dos membros de [1,2,3]
.
Por exemplo:
/* .. seu código para inBetween e inArray */ seja arr = [1, 2, 3, 4, 5, 6, 7]; alerta( arr.filter(inBetween(3, 6)) ); //3,4,5,6 alerta( arr.filter(inArray([1, 2, 10])) ); //1,2
Abra uma sandbox com testes.
função entre(a, b) { função de retorno(x) { retornar x >= a && x <= b; }; } seja arr = [1, 2, 3, 4, 5, 6, 7]; alerta( arr.filter(inBetween(3, 6)) ); //3,4,5,6
função inArray(arr) { função de retorno(x) { retornar arr.includes(x); }; } seja arr = [1, 2, 3, 4, 5, 6, 7]; alerta( arr.filter(inArray([1, 2, 10])) ); //1,2
Abra a solução com testes em uma sandbox.
importância: 5
Temos uma série de objetos para classificar:
deixe os usuários = [ {nome: "John", idade: 20, sobrenome: "Johnson" }, {nome: "Pete", idade: 18, sobrenome: "Peterson" }, {nome: "Ann", idade: 19, sobrenome: "Hathaway" } ];
A maneira usual de fazer isso seria:
// por nome (Ann, John, Pete) usuários.sort((a, b) => a.nome > b.nome ? 1 : -1); // por idade (Pete, Ann, John) usuários.sort((a, b) => a.idade > b.idade ? 1 : -1);
Podemos torná-lo ainda menos detalhado, assim?
usuários.sort(byField('nome')); usuários.sort(byField('idade'));
Então, em vez de escrever uma função, basta colocar byField(fieldName)
.
Escreva a função byField
que pode ser usada para isso.
Abra uma sandbox com testes.
function byField(fieldName){ return (a, b) => a[nomedocampo] > b[nomedocampo] ? 1: -1; }
Abra a solução com testes em uma sandbox.
importância: 5
O código a seguir cria uma matriz de shooters
.
Cada função destina-se a gerar seu número. Mas algo está errado…
function makeArmy() { deixe atiradores = []; seja i = 0; enquanto (eu <10) { let shooter = function() { // cria uma função de atirador, alerta(eu); // que deve mostrar seu número }; atiradores.push(atirador); // e adicione ao array eu++; } // ...e retorna o array de atiradores atiradores de retorno; } deixe exército = makeArmy(); // todos os atiradores mostram 10 em vez dos números 0, 1, 2, 3... exército[0](); // 10 do atirador número 0 exército[1](); // 10 do atirador número 1 exército[2](); // 10 ...e assim por diante.
Por que todos os atiradores mostram o mesmo valor?
Corrija o código para que funcione conforme o esperado.
Abra uma sandbox com testes.
Vamos examinar o que exatamente acontece dentro makeArmy
e a solução se tornará óbvia.
Ele cria um array vazio shooters
:
deixe atiradores = [];
Preenche-o com funções via shooters.push(function)
no loop.
Cada elemento é uma função, então o array resultante fica assim:
atiradores = [ função () { alerta (i); }, função () { alerta (i); }, função () { alerta (i); }, função () { alerta (i); }, função () { alerta (i); }, função () { alerta (i); }, função () { alerta (i); }, função () { alerta (i); }, função () { alerta (i); }, função () { alerta (i); } ];
A matriz é retornada da função.
Então, mais tarde, a chamada para qualquer membro, por exemplo, army[5]()
obterá o elemento army[5]
do array (que é uma função) e o chamará.
Agora, por que todas essas funções mostram o mesmo valor, 10
?
Isso ocorre porque não há variável local i
dentro das funções shooter
. Quando tal função é chamada, ela retira i
de seu ambiente lexical externo.
Então, qual será o valor de i
?
Se olharmos para a fonte:
function makeArmy() { ... seja i = 0; enquanto (eu <10) { let atirador = function() { //função atirador alerta(eu); //deve mostrar seu número }; atiradores.push(atirador); //adiciona função ao array eu++; } ... }
Podemos ver que todas as funções shooter
são criadas no ambiente lexical da função makeArmy()
. Mas quando army[5]()
é chamado, makeArmy
já terminou seu trabalho e o valor final de i
é 10
( while
para em i=10
).
Como resultado, todas as funções shooter
obtêm o mesmo valor do ambiente lexical externo, ou seja, o último valor, i=10
.
Como você pode ver acima, a cada iteração de um bloco while {...}
, um novo ambiente léxico é criado. Então, para corrigir isso, podemos copiar o valor de i
em uma variável dentro do bloco while {...}
, assim:
function makeArmy() { deixe atiradores = []; seja i = 0; enquanto (eu <10) { seja j = eu; let atirador = function() { //função atirador alerta( j ); //deve mostrar seu número }; atiradores.push(atirador); eu++; } atiradores de retorno; } deixe exército = makeArmy(); // Agora o código funciona corretamente exército[0](); //0 exército[5](); //5
Aqui, let j = i
declara uma variável “local de iteração” j
e copia i
nela. Os primitivos são copiados “por valor”, então na verdade obtemos uma cópia independente de i
, pertencente à iteração do loop atual.
Os atiradores funcionam corretamente, porque o valor de i
agora está um pouco mais próximo. Não no ambiente lexical makeArmy()
, mas no ambiente lexical que corresponde à iteração do loop atual:
Tal problema também poderia ser evitado se for
no início, assim:
function makeArmy() { deixe atiradores = []; for(seja i = 0; i < 10; i++) { let atirador = function() { //função atirador alerta(eu); //deve mostrar seu número }; atiradores.push(atirador); } atiradores de retorno; } deixe exército = makeArmy(); exército[0](); //0 exército[5](); //5
É essencialmente a mesma coisa, porque for
a cada iteração gera um novo ambiente léxico, com sua própria variável i
. Portanto, shooter
gerado em cada iteração faz referência ao seu próprio i
, daquela mesma iteração.
Agora, como você se esforçou tanto para ler isso, e a receita final é tão simples – basta usar for
, você pode se perguntar – valeu a pena?
Bem, se você pudesse responder facilmente à pergunta, não leria a solução. Então, espero que esta tarefa tenha ajudado você a entender as coisas um pouco melhor.
Além disso, há de fato casos em que se prefere while
for
e outros cenários em que tais problemas são reais.
Abra a solução com testes em uma sandbox.