Encerramento Js. Fechamentos em JavaScript: um exemplo prático, recursos e regras

Olá pessoal! Neste artigo, veremos, o que é um encerramento em javascript.

Este é um tópico bastante simples, mas requer algum entendimento. Primeiro, vamos dar uma olhada no que está acontecendo dentro da função.

Saudação de função (nome) (
// LexicalEnvironment = (nome: "Nikolay", texto: indefinido)
var text = "Olá" + nome;
// LexicalEnvironment = (nome: "Nikolay", texto: "Olá, Nikolay")
alerta (texto);
}

Saudação ("Nikolay");

O que está acontecendo aqui e o que está LexicalEnvironment? Vamos descobrir.

Quando uma função é chamada, um objeto é criado para ela LexicalEnvironment, em que todas as variáveis ​​e funções locais são gravadas, bem como uma referência ao escopo externo (mais sobre isso mais tarde). No nosso caso, temos uma variável local nome, que imediatamente tem um significado (aquele que transmitimos) e este é "Nikolai". Já escrevi em um dos artigos, mas deixe-me lembrar que o intérprete sabe tudo sobre todas as variáveis ​​de antemão. É por isso que já temos uma variável no início da função texto, o intérprete sabe disso, mas como ainda não alcançamos a atribuição de algum valor a esta variável, é igual a Indefinido... Agora atribuímos um valor à variável, e nosso objeto LexicalEnvironment está mudando. Sua propriedade texto torna-se igual ao que escrevemos ("Olá, Nikolai" no nosso caso). Depois que a função for concluída, o objeto LexicalEnvironment destruído. Em chamadas subsequentes para a função, ela será criada novamente, etc.

Agora vamos para o próximo exemplo. O que será exibido neste caso?

Var b = 2;
função x (a) (
alerta (a + b);
}
x (1);

Você já pensou? Acho que a maioria respondeu que o número 3 será mostrado, e essa é a resposta correta, mas você pode dizer como o intérprete descobriu sobre a variável b? Afinal, não está no corpo da função. Se não, vamos descobrir.

Na verdade, em javascript existe uma propriedade oculta chamada [] ... Quando uma função é declarada, ela sempre é declarada em algum lugar. Esta função pode estar em outra função, pode estar em um objeto global, etc. No nosso caso, a função é declarada no objeto global janela, portanto, a propriedade x. [] = janela.

Var b = 2;
função x (a) (// x. [] = janela
// LexicalEnvironment = (a: 1) -> janela
alerta (a + b);
}
x (1);

Esta flecha no objeto LexicalEnvironmenté uma referência ao escopo externo e esta referência é definida pela propriedade [] ... Assim, no objeto LexicalEnvironment teremos um link para um objeto externo janela... Quando o interpretador procura por uma variável, ele primeiro a procura no objeto LexicalEnvironment, então, se ele não encontrou uma variável, então ele olha para o link, vai para o escopo externo e procura por lá e assim por diante até o fim. Se ele não encontrou essa variável em nenhum lugar, haverá um erro. Em nosso caso, a variável uma o intérprete vai tirar do objeto LexicalEnvironment, e a variável b do objeto janela... Claro, se tivermos uma variável local b com algum valor, então será escrito no objeto LexicalEnvironment e subseqüentemente será tirado de lá, e não do escopo externo.

IMPORTANTE! Lembre-se de que a propriedade [] é definido para o local onde a função foi declarada, e não chamada, por isso o código abaixo imprimirá o número 3, e não 5, como alguns podem pensar.

Barra b = 2;
função x (a) (
alerta (a + b);
}

Função y () (
var b = 4;
x (1);
}

Tudo isso foi um prelúdio apenas para que você entenda como tudo funciona e seja mais fácil para você entender como funcionam os fechamentos. E agora vamos direto ao tópico do artigo.

Como eu disse, o objeto LexicalEnvironment destruída toda vez que a função é executada e criada novamente quando lembrar... No entanto, e se quisermos armazenar esses dados? Aqueles. nós queremos tudo o que está escrito em LexicalEnvironment agora, preservado e usado para as próximas ligações? É por isso que existem fechamentos.

Saudação de função (nome) (
// LexicalEnvironment = (nome: "Nikolay")
return function () (// [] = LexicalEnvironment
alerta (nome);
};
}

Var func = saudação ("Nikolay");
saudação = nulo;
func ();

Vamos ver o que fizemos. Primeiro, criamos uma função saudações para o qual o nome é passado. Um objeto é criado na função LexicalEnvironment onde a propriedade é criada (nossa variável local) nome e ela recebeu o nome de "Nikolai". E agora é importante: retornamos da função outra função, dentro da qual saímos por meio de alerta variável nome... Em seguida, atribuímos à variável função o valor retornado da função saudações, e esse valor é a nossa função que imprime o nome. agora nós saudações atribuir nulo, ou seja, nós apenas destruímos nossa função saudações no entanto, quando ligamos função, então veremos o valor da variável nome("Nikolay") funções saudações... Como isso é possível, você diz? É muito simples. O ponto é que nossa função de retorno também tem a propriedade [] , que se refere ao escopo externo, e este escopo externo no nosso caso é um objeto LexicalEnvironment nossa função saudações... Portanto, apesar de termos removido nossa função saudações, um objeto LexicalEnvironment não é excluído e permanece na memória e permanecerá na memória enquanto houver pelo menos uma referência a ele. Temos este link - nossa função de retorno que usa a variável nome este objeto LexicalEnvironment.

Então, vamos agora definir o que o que é o fechamento.

Fecho- uma função junto com todas as variáveis ​​que estão disponíveis para ela.

Bem, o artigo acabou por ser bastante volumoso, mas isso só porque tentei descrever o mais detalhadamente possível todo o processo de fechamento. Para consolidação, quero dar um exemplo simples - um contador usando o tópico que acabei de aprender. Por favor, entenda o código e escreva nos comentários como e por que ele funciona. Se não entender algo, você também pode fazer uma pergunta. Obrigado pela atenção!

Função makeCounter () (
var currentCount = 0;

Função de retorno () (
currentCount ++;
return currentCount;
};
}

Var counter = makeCounter ();
contador ();
contador ();
alerta (contador ()); // 3

V Funções JavaScript podem ser descritos não apenas um após o outro, mas também um dentro do outro. Quando você tem uma função dentro de outra, a função interna tem acesso às variáveis ​​da função externa.

Função externa (x) (var tmp = 3; função interna (y) (alerta (x + y + (++ tmp)); // exibe 16) interno (10);) externo (2);

Este código sempre retorna 16, porque a função interna vê x, que é uma variável na função externa. V este caso argumento da função. Além disso, inner () pode ver tmp de outer ().

Isso é chamado de fechamento ou fechamento. Mais precisamente, uma função externa é chamada de fechamento e tudo dentro dela é chamado de ambiente de fechamento.

Às vezes dizem que um closure é uma função que retorna uma função, isso está errado, para chamar uma função de closure basta que a função interna acesse uma variável de fora do seu escopo.

Função foo (x) (var tmp = 3; função de retorno (y) (alerta (x + y + (++ tmp)); // também alerta 16)) var bar = foo (2); // bar agora é um fechamento. bar (10);

A função acima também retornará 16, porque mesmo após a conclusão de foo, bar ainda tem acesso a x e tmp, mesmo se a própria variável bar não estiver dentro do escopo em que foram declaradas.

No entanto, como a variável tmp ainda está dentro do fechamento de bar, ela continua a incrementar cada vez que a barra é chamada.

Aqui exemplo mais simples fechamentos:

Var a = 10; function test () (console.log (a); // saída 10 console.log (b); // saída 6) var b = 6; teste ();

Quando uma função é executada em JavaScript, é criado um ambiente para ela, ou seja, uma lista de todas as variáveis ​​visíveis para ela, não apenas os argumentos e variáveis ​​declarados dentro dela, mas também fora, neste exemplo são "a" e B".

Você pode criar mais de um encerramento em um ambiente, retornando-os como uma matriz, objeto ou vinculando-os a variáveis ​​globais. Neste caso, todos trabalharão com o mesmo valor x ou tmp sem criar cópias separadas.

Como em nosso exemplo x é um número, seu valor copiado em foo como seu argumento x.

Por outro lado, JavaScript sempre usa referências ao passar objetos. Se você chamar foo com um objeto como argumento, o encerramento retornado retornará uma referência ao objeto original!

Função foo (x) (var tmp = 3; função de retorno (y) (alert (x + y + tmp); x.memb = x.memb? X.memb + 1: 1; alert (x.memb);) ) var age = novo número (2); var bar = foo (idade); // bar agora é uma idade de referência de fechamento. bar (10);

Como você pode esperar, cada chamada para bar (10) incrementa x.memb. O que você não pode esperar é que x continue a se referir ao mesmo objeto que idade! Após duas ligações para bar, age.memb fará 2! A propósito, é assim que ocorrem vazamentos de memória em objetos HTML.

Na programação, um encerramento, ou na versão em inglês, "encerramento" é um método de implementação do nome contextual de uma ligação em uma linguagem de função de primeira classe. Operacionalmente, é um registro que armazena uma função junto com o ambiente. O ambiente é o mapeamento de cada função livre para um valor ou referência por nome criado por um closure em Javascript. Permite o acesso às variáveis ​​capturadas, por meio de cópias de valores ou referências, mesmo quando chamadas fora do escopo.

Conceito de fechamento

Os closures foram desenvolvidos na década de 1960 para avaliar mecanicamente expressões em cálculo e aplicados em 1970 como um recurso da linguagem de programação PAL para oferecer suporte a funções de escopo lexical de primeira classe. Peter Landin definiu o termo "fechamento" em 1964 com um ambiente e parte de controle usado no motor SECD para avaliar expressões lambda vinculadas ao ambiente léxico, resultando em seu fechamento ou fechamento em Javascript.

Essa explicação surgiu em 1975 como uma versão lexicamente restrita do LISP e se tornou generalizada. O ambiente léxico é o conjunto de variáveis ​​válidas em um programa. Consiste em um ambiente léxico interno e referências a um ambiente externo denominado variáveis ​​não locais.

Os fechamentos lexicais em Javascript são funções com seu ambiente externo. Como em JavaScript, todas as variáveis ​​têm uma referência de tipo. JS usa apenas vinculação por referência - que é o mesmo que em C ++ 11, e o tempo de vida de variáveis ​​não locais capturadas por uma função se estende até o tempo de vida da função.

Fechamentos em Javascript geralmente aparecem em linguagens com significados de primeira classe. Essas linguagens permitem que funções sejam passadas como argumentos. E também retornar de chamadas de função e vincular a nomes de variáveis. Isso é semelhante a tipos simples, como strings e inteiros.

Neste exemplo, a expressão lambda (lambda (livro) (> = (limite de livro de vendas de livro))) aparece dentro da função de livros mais vendidos. Quando uma expressão lambda é avaliada, o esquema cria um encerramento que consiste no código para a expressão lambda e uma referência à variável de limite, que é uma variável livre dentro da expressão lambda. O fechamento é então passado para a função de filtro, que o chama repetidamente para determinar quais livros devem ser adicionados à lista de resultados e quais devem ser descartados.

Uma vez que há um fechamento no valor do limite, o último pode usá-lo toda vez que o filtro o chama. A função de filtro em si pode ser definida de uma forma completamente arquivo separado... Aqui está o mesmo exemplo reescrito em JS. Ele demonstra como os fechamentos funcionam nos bastidores em Javascript.

A palavra-chave é usada aqui em vez da função de filtro global, mas por outro lado, a estrutura e o efeito do código são os mesmos. A função pode criar um fechamento e retorná-lo, pois neste caso ele sobrevive à execução da função com as variáveis ​​f e dx continuam a funcionar após a derivada, mesmo que a execução tenha saído de seu escopo e elas não estejam mais visíveis.

Em linguagens sem encerramentos, o tempo de vida de uma variável local automática é o mesmo que a execução do quadro de pilha onde a variável é declarada. Em linguagens com encerramentos Javascript e funções iife, as variáveis ​​devem continuar a existir, desde que qualquer bloqueio existente seja referenciado. Isso geralmente é feito usando alguma forma de coleta de lixo.

A vantagem de um encerramento é que ele preserva o escopo, a "cadeia de visão" do contexto de execução externo ou "pai". Esse comportamento pode ser usado de várias maneiras e se tornou uma ferramenta útil para prevenir uma série de erros de JavaScript. Um dos mais comuns é o problema de loop.

O problema do loop ocorre quando o usuário cria uma função em um loop e espera que o valor atual de uma variável permaneça neste nova função mesmo que mude no contexto dos loops antes de chamar a nova função. Fechamentos usados ​​dessa forma não têm mais transparência referencial e, portanto, não são mais funções puras; no entanto, eles são comumente usados ​​em linguagens funcionais impuras, como Scheme. Para entender o que são fechamentos em Javascript, você precisa considerar seus casos de uso. Na verdade, na prática, eles têm muitos usos:

  1. Eles podem ser usados ​​para definir estruturas de governança. Por exemplo, todas as estruturas de controle Smalltalk padrão, incluindo branches (if / then / else) e loops (while e for), são definidas usando objetos cujos métodos aceitam fechamentos. Os usuários também podem usar facilmente os fechamentos para definir a estrutura de controle. Em idiomas de destino, você pode criar um ambiente rico para isso, permitindo que você se comunique com confiança e mude esse ambiente. O fechamento é usado para implementar sistemas de objetos.
  2. Crie métodos de variável privada e pública usando modelos de módulo. Como as funções retornadas herdam o escopo da função pai, elas estão disponíveis para todas as variáveis ​​e argumentos naquele contexto.
  3. É útil em uma situação em que uma função usa o mesmo recurso para cada chamada, mas também cria o próprio recurso para ela. Essa circunstância torna o método ineficaz, o que pode ser eliminado apenas pelo fechamento.

De acordo com o Mozilla Developer Network (MDN), "Closures são funções com variáveis ​​independentes que 'lembram' seu ambiente de criação." E geralmente, quando uma função é encerrada, suas variáveis ​​locais não existem mais. Você pode entender como os fechamentos funcionam em Javascript observando vários mecanismos. O primeiro é a lógica formal. Por exemplo, usando a função logName, que recebe um nome como parâmetro e o registra. Em seguida, crio um loop for para iterar a lista de nomes, defino o primeiro tempo limite e chamo a função logName passando o nome atual.

Em uma linguagem de primeira classe, as funções podem ser manipuladas da mesma maneira que outros tipos de dados, como int ou string. Somente este mecanismo permite que muitos criem coisas incríveis, por exemplo, atribuir uma função a uma variável para chamá-la mais tarde, ou passá-la como um parâmetro para outra função.

Este princípio é usado por muitos frameworks, bem como por manipuladores de eventos DOM. Primeiro, eles "ouvem" o evento e, em seguida, atribuem uma função de retorno de chamada que será chamada sempre que o evento for disparado.

Uma função anônima é uma função sem nome. Programadores praticamente novatos os encontram todos os dias, não entendendo o jogo com números. Por exemplo, ao realizar uma operação de adição, você pode navegar por variáveis, por exemplo:

  • var x = 3;
  • y = 5;
  • var z = x + y.

Ou se você não pretende reprocessar os números: var z = 3 + 5;

Esses são os números anônimos. Para funções anônimas, você pode declará-las quando forem usadas dinamicamente - sem passar uma variável. Por exemplo, pegue a função do anterior:

(alerta ("Ceci est une fonction anonyme.");

Além disso, existe uma sintaxe de declaração de função alternativa que enfatiza que ambas as funções podem ser anônimas e podem referir-se a variáveis ​​simples, o que é uma maneira conveniente de configurar uma função de retorno de chamada.

Na verdade, este é o mesmo mecanismo, mas deste ponto de vista, permitirá que você veja como a função é fechada por dentro. Como você pode ver, uma vez que funções são variáveis ​​como outras, não há razão para que você não possa defini-las localmente. Em uma linguagem de ordem zero, como C, C ++ e Java, todas as funções são definidas no mesmo nível de visibilidade, na mesma classe ou globalmente. Por outro lado, em JavaScript, a função local desaparece, como outras variáveis ​​locais, assim que a função pai termina, portanto, não é visível de outras funções.

Na verdade, é complicado, mas o JavaScript tem uma maneira de controlar a visibilidade das variáveis ​​e de duas maneiras. A atribuição de uma variável global em JavaScript tem o mesmo mecanismo que em Java - objetos complexos, matrizes, elementos DOM e outros são passados ​​por referência, portanto, no seguinte código:

var tab =; var tab2 = tab.

Onde, tab e tab2 são duas referências à mesma tabela, tecnicamente são ponteiros coletados pelo lixo. As funções também são passadas por referência. A variável globalFn não está mais oculta. O pedido permite que você faça isso, conforme demonstrado na tarefa de encerramento de Javascript.

Veja como extrair uma função do contexto local se a função satisfizer outras variáveis ​​locais. Um exemplo simples: incremento automático, uma função que retorna um inteiro que aumenta em 1 cada vez que é chamada. Especificamente, precisamos de uma função inc que se comporte assim:

// retourne 0 inc ();

// retourne 1 inc ();

// retourne 2 inc ();

Com um fechamento, fica assim:

função makeInc () (var x = 0; função de retorno () (retorno x ++;)) var inc = makeInc ();

Na última linha, no momento em que a função variável inc é criada, ela carrega algumas variáveis ​​que estão ao redor, neste caso x. Ele cria um objeto invisível em torno da função que contém essa variável. Este objeto é uma função de fechamento de Javascript. Além disso, cada cópia da função terá seu próprio encerramento:

var inc1 = makeInc ();

var inc2 = makeInc ();

Como você pode ver, o fechamento é muito útil em muitos casos.

Para evitar conflitos de nomes de variáveis, os namespaces são comumente usados. Em JavaScript, os namespaces são objetos como qualquer outro.

Naturalmente, A.xe B.x não são a mesma variável. No entanto, se você só precisa executar o script sem exigir que as variáveis ​​sejam salvas para o resto, você pode usar uma função anônima como um encerramento. Isso dá uma sintaxe um tanto estranha. Embora as duas linhas de código no meio sejam bastante comuns, por outro lado, a função que está ao redor é executada instantaneamente. Preste atenção aos parênteses () no final. E para poder fazer um fechamento, função anônima em si deve estar entre parênteses.

Esta função anônima usa uma variável local, parágrafo. Esta é uma ótima maneira de evitar colisões de nomes ou falta de jeito, mas também contra Ataques XSS as variáveis ​​do usuário são protegidas, ninguém pode alterá-las para afetar o comportamento do script.

Existe uma variante: (function () (// ...) ());

Ao mesmo tempo, é dada atenção à permutação dos parênteses. A diferença entre essas duas opções é um tanto difícil de explicar, pois tem a ver com a forma como o código é lido pelo analisador léxico. Em ambos os casos, a função é considerada uma expressão, mas essa expressão não é avaliada ao mesmo tempo. Você só precisa lembrar que são necessários dois pares de parênteses: um ao redor da função e outro atrás dela.

Programação Javascript em loops

Quando um usuário está fazendo uma grande quantidade de programação Javascript, é difícil para ele evitar loops. Isso enlouquece algumas pessoas, após o que elas chegam à conclusão de que toda implementação de Javascript contém um erro grave. Se o desenvolvedor já tem um loop que não deseja converter para usar a função iteradora, tudo o que ele precisa fazer é um encerramento no qual ele define novas variáveis. Eles fixam o valor atual das variáveis ​​e mudam a cada iteração. O truque para capturar variáveis ​​é que o fechamento externo é executado imediatamente durante a iteração do loop atual. Uma dessas duas abordagens de exemplo pode ser usada

Agora há outra solução simplificada para esse problema, pois a palavra-chave let é suportada tanto no Firefox quanto no Chrome. Isto é palavra-chave em vez de bloco de variável var. Let funciona de uma maneira mágica, porque uma nova variável j é declarada, o valor de i da qual é fixado por um fechamento dentro do loop. No entanto, deve-se ter em mente que ele não continua existindo após o término de uma iteração do loop, por ser local.

Loop e função

Um loop for não é representado em JavaScript, o mesmo que um loop for em C ou Java. Na verdade, parece mais com PHP. O conhecimento mais importante sobre loops em JS é que eles não criam escopo. JS não tem um bloco de escopo, apenas uma função de escopo. Essa propriedade pode ser vista no seguinte snippet:

função foo () (var bar = 1;

para (var i = 0; i< 42; i++) {var baz = i;} /* more code */}

É claro que a barra está disponível em toda a função. Antes da primeira iteração do loop, baz será indefinido. Após o loop, ele terá um valor de 41 (e i será de 42). Portanto, qualquer variável declarada em qualquer lugar da função estará disponível em qualquer lugar da função e só terá um valor após ter sido atribuído a ela.

Portões e agregação

Um encerramento nada mais é do que funções, dentro de outras funções, e passado para algum outro contexto. São chamados de fechamentos porque se fecham por meio de variáveis ​​locais, ou seja, estão disponíveis para outras funções do escopo. Por exemplo, o tempo x é especificado como o parâmetro foo e var bar = foo (2) () retornará 84.

A função retornada foo tem acesso x. Isso é muito importante porque ajuda os desenvolvedores a criar funções dentro de loops que dependem das variáveis ​​do loop. Considere este snippet, que atribui um manipulador de cliques a vários elementos:

// elementos é uma matriz de 3 elementos DOM var values ​​= ["foo", "bar", "baz"];

eu< l; i++) {var data = values[i];

elementos [i] .onclick = function () (alerta (dados);

O valor que eles usarão para alert quando clicados será o mesmo para todos, ou seja, baz. No momento em que o manipulador de eventos é chamado, o for já está completo. JS não tem escopo de bloco, ou seja, todos os manipuladores usam uma referência à mesma variável de dados. Após o loop, este valor será um valor. Cada declaração de variável cria um local de armazenamento na memória. Em for, esses dados mudam continuamente, a posição na memória permanece inalterada.

Cada manipulador de eventos tem acesso ao mesmo local de memória. A única solução é introduzir outra área que "captura" o valor atual dos dados. JS tem apenas um escopo de função. Portanto, outra função é introduzida. Exemplo:

função createEventHandler (x) (função de retorno () (alert (x);

para (var i = 0, l = elements.length;

eu< l; i++) {var data = values[i];

elementos [i] .onclick = createEventHandler (dados);

Isso funciona porque o valor dos dados será armazenado no escopo local, createEventHandler e esta função é executada a cada iteração. Isso pode ser escrito de forma mais curta, usando funções executáveis ​​imediatamente:

para (var i = 0, l = elements.length;

eu< l; i++) {var data = values[i];

elementos [i] .onclick = (função (x) (função () (alerta (x);

Exemplo prático de encerramento em Javascript

Se o usuário fechar diretamente acima do código no navegador, ele pode ter um problema, pois pode cometer qualquer erro de sintaxe. Se ele executar o código diretamente no navegador, as chances de não compilar o processo de compilação do webpack são muito altas. Soluções possíveis:

função trabalho (nome) (

função de retorno (tópico) (

console.log (O que é $ (tópico) em $ (nome));

work ("Javascript") ("Encerramento");

A função é chamada primeiro e o argumento do nome é passado. Agora, essa função de vocabulário também retorna uma função que também recebe um argumento de tópico. Esta função registra a saída, e a saída tem acesso à variável.

O escopo de funções do Insider não se limita a esta função, por isso o conceito é denominado Fechamento, pois tem acesso a este escopo parâmetro externo... A função retornada tem acesso ao escopo ou contextos lexicais externos. Quando um desenvolvedor chama uma função que também a retorna, então chama primeiro variáveis ​​de função sempre disponível para função interna. Aqui está um exemplo com o código a seguir.

Exemplo de função interna

Para obter mais informações sobre fechamentos em Javascript, consulte o segundo exemplo. Este tempo de execução agora está destruído, mas o nome do parâmetro ainda existe. Um novo ambiente funcional interno é criado, que é uma função anônima. Ela tem acesso à área do ambiente lexical externo.

Assim, na variável de ambiente externo ainda existe para que a função anônima que tem acesso à variável nome imprima no console, por exemplo, "O que é um encerramento em Javascript". Função anônima interna //main.js

fábrica de funções () (var produtos =;

i ++) (produtos.push (função () (console.log (i);

) devolver produtos;

) sabonete var = fábrica ();

O resultado deste exemplo é bastante insignificante e igual a 2.

Quando soap - soap () é chamado de variável de contexto externa, sempre 2 porque no loop a condição é falsa em i<2, поэтому при этом значение i равно 2, а во время вызова нужно напечатать значение в консоль так, она всегда пишет 2. То же самое для мыла - soap ().

Crie funções dinamicamente

Você pode criar uma fábrica de funções, functionFactory, que executa tarefas personalizadas. A função resultante da fábrica de funções será um encerramento que lembra o ambiente de criação.

var functionFactory = function (num1) (função de retorno (num2) (retornar num1 * num2;

O acima permite que um número seja passado para a functionFactory. FunctionFactory então retorna um Closure que lembra o valor num1. A função resultante multiplica o original num1 vezes o valor de num2, que é passado na chamada.

var mult5 = functionFactory (5);

var mult10 = functionFactory (10);

O acima apenas cria as funções mult5 e mult10. Agora você pode se referir a qualquer uma dessas funções passando um novo número que precisa ser multiplicado por 5 ou 10. Agora você pode ver o resultado.

O fechamento é um dos recursos mais poderosos do javascript, mas não pode ser usado corretamente sem a compreensão da essência. É relativamente fácil criá-los por acidente, razão pela qual encerramentos de Javascript são perigosos. Sua criação tem consequências potencialmente prejudiciais, especialmente em alguns ambientes de navegador da web relativamente comuns. Para evitar encontrar desvantagens acidentalmente e aproveitar os benefícios que elas oferecem, você precisa entender seu mecanismo.

Neste artigo, tentarei explicar escopos e encerramentos em JavaScript, que é onde muitos acham difícil.

Introdução

Existem alguns artigos na rede que tentam explicar escopos e encerramentos, mas em geral, eu diria que a maioria deles não é totalmente clara. Além disso, alguns artigos presumem que você tenha programado anteriormente em 15 outras linguagens, embora eu acredite que a maioria das pessoas que escrevem em JavaScript têm experiência apenas em HTML e CSS, não em C ou Java.

Portanto, o objetivo deste artigo é explicar a todos o que são escopo e fechamento, como funcionam e, o mais importante, quais são suas vantagens. Antes de ler este artigo, você precisa conhecer os conceitos básicos de variáveis ​​e funções em JavaScript.

Área de visibilidade

Escopo significa onde as variáveis ​​e funções estão disponíveis e em que contexto são executadas. Uma variável ou função pode ser definida no escopo global ou local. As variáveis ​​têm o chamado escopo de função e as funções têm o mesmo escopo que as variáveis.

Âmbito global

Quando algo é global, significa que pode ser acessado de qualquer lugar em seu código. Vamos considerar um exemplo:

var monkey = "Gorila"; function greetVisitor () (return alert ("Olá caro leitor do blog!");)

Se este código fosse executado em um navegador web, então o escopo seria janela, então estará disponível para tudo que for executado em janela.

Escopo local

Ao contrário do escopo global, o escopo local é quando algo é definido e está disponível apenas em alguma parte do código, como uma função. Vamos considerar um exemplo:

function talkDirty () (var said = "Oh, seu pequeno amante do VB, você"; return alert (dizendo);) alert (dizendo); // Lança um erro

Neste exemplo, a variável ditado está disponível apenas dentro da função talkDirty, fora da qual é indefinida. Nota: Se você definiu um ditado sem a palavra-chave var, ele se tornaria automaticamente global.

Além disso, se você tiver funções aninhadas, a função interna terá acesso às funções nas quais está aninhada, bem como às variáveis:

função saveName (firstName) (função capitalizeName () (return firstName.toUpperCase ();) var capitalized = capitalizeName (); return capitalize;) alert (saveName ("Robert")); // Retorna "ROBERT"

Como você acabou de ver, a função interna capitalizeName não precisa passar nenhum parâmetro, pois ele tem acesso total ao parâmetro firstName na função externa saveName. Para maior clareza, considere outro exemplo:

function siblings () (var siblings = ["John", "Liza", "Peter"]; function siblingCount () (var siblingsLength = siblings.length; return siblingsLength;) function joinSiblingNames () (retornar "Eu tenho" + siblingCount () + "siblings: nn" + siblings.join ("n");) return joinSiblingNames ();) alert (siblings ()); // Outputs "Eu tenho 3 irmãos: John Liza Peter"

Como você pode ver, ambas as funções internas têm acesso à matriz de irmãos e cada função interna tem acesso a outra função interna de irmão (neste caso, joinSiblingNames tem acesso a siblingCount). No entanto, a variável siblingsLength dentro de siblingCount está disponível apenas dentro desta função, ou seja, neste âmbito.

Fecho

Agora que você tem uma compreensão mais clara dos escopos, vamos adicionar fechamentos a eles. Fechamentos são expressões, geralmente funções, que podem operar em um conjunto de variáveis ​​dentro de um contexto específico. Ou, em termos mais simples, funções internas que se referem a variáveis ​​locais de funções externas formam fechamentos. Por exemplo:

função add (x) (retornar função (y) (retornar x + y;);) var add5 = add (5); var no8 = add5 (3); alerta (no8); // Retorna 8

Caramba! O que está acontecendo aqui? Vamos descobrir:

1. Quando chamamos a função add, ela retorna uma função.

2. Esta função fecha o contexto e lembra qual era o parâmetro x naquele momento (ou seja, neste caso, o valor 5)

3. Quando o resultado da função add for atribuído à variável add5, ela sempre saberá qual era x quando a variável foi criada.

4. A variável add5 se refere a uma função que sempre adicionará o valor 5 a qualquer argumento passado a ela.

5. Isso significa que quando chamamos add5 com o valor 3, ele adicionará os números 5 e 3 e retornará 8.

Na verdade, no mundo do JavaScript, a função add5 se parece com isto:

função add5 (y) (retornar 5 + y;)

O notório problema do loop
Quantas vezes você criou loops em que queria atribuir o valor de i de alguma forma, por exemplo, a um elemento, e percebeu que apenas o último valor de i foi retornado?

Erradoapelo

Vamos dar uma olhada neste código incorreto que cria 5 elementos , adiciona o valor i como texto a cada elemento e onclick, que deve emitir um alerta com o valor i para o link fornecido, ou seja, o mesmo significado que no texto do elemento. Os elementos são então adicionados ao corpo do documento:

<5; i++) { link = document.createElement("a"); link.innerHTML = "Link " + i; link.onclick = function () { alert(i); }; document.body.appendChild(link); } } window.onload = addLinks;

Cada elemento contém o texto correto, ou seja, “Link 0 ″,“ Link 1 ″ etc. Mas seja qual for o link em que você clicar, será exibido um alerta com o número 5. Qual é o problema? A razão é que o valor da variável i aumenta em 1 a cada iteração do loop, e desde o evento onclick não é executado, mas simplesmente aplicado ao elemento , então o valor aumenta.

Portanto, o loop continua até que i seja igual a 5, que é o último valor antes de sair da função addLinks. Além disso, toda vez que o evento onclick é disparado, o último valor de i é obtido.

Manuseio correto

O que você precisa fazer é criar um fechamento. Como resultado, quando você aplica o valor i ao evento onclick do elemento , então o valor i será atribuído naquele momento. Por exemplo, assim:

função addLinks () (para (var i = 0, link; i<5; i++) { link = document.createElement("a"); link.innerHTML = "Link " + i; link.onclick = function (num) { return function () { alert(num); }; }(i); document.body.appendChild(link); } } window.onload = addLinks;

Usando este código, se você clicar no primeiro item, o alerta exibirá "0", o segundo exibirá "1" e assim por diante. A solução é que a função interna aplicada ao evento onclick cria um encerramento no qual o parâmetro num é chamado, ou seja, ao valor de i naquele momento no tempo.

Esta função "lembra" o valor desejado e pode então retornar o dígito correspondente quando o evento onclick for disparado.

Funções de execução segura

Funções de execução segura são funções que começam a ser executadas imediatamente e criam seu próprio encerramento. Vamos considerar um exemplo:

(função () (var cão = "pastor alemão"; alerta (cão);)) (); alerta (cão); // retorna indefinido

Portanto, a variável cão só está disponível neste contexto. Pensem, a variável oculta é cachorro ... Mas, meus amigos, é aqui que a diversão começa! Isso resolveu nosso problema de looping e também é a base para o padrão de módulo JavaScript do Yahoo.

Padrão de Módulo Yahoo JavaScript

A essência desse padrão é que ele usa uma função de execução segura para criar um encerramento, portanto, torna possível usar propriedades e métodos privados e públicos. Exemplo simples:

var person = function () (// Private var name = "Robert"; return (getName: function () (return name;), setName: function (newName) (name = newName;));) (); alerta (pessoa.nome); // Alerta indefinido (person.getName ()); // "Robert" person.setName ("Robert Nyman"); alert (person.getName ()); // "Robert Nyman"

A vantagem dessa abordagem é que você pode definir por si mesmo o que será aberto em seu objeto (e pode ser alterado) e o que é fechado, o que não pode ser acessado ou alterado por ninguém. A variável name está oculta fora do contexto da função, mas está disponível para as funções getName e setName, uma vez que eles criam fechamentos que fazem referência ao nome da variável.

Conclusão

Espero sinceramente que, depois de ler este artigo, tanto os novatos quanto os programadores experientes tenham uma compreensão mais clara de como escopos e encerramentos funcionam em JavaScript. Perguntas e comentários são bem-vindos, e se você tiver algo importante para relatar, posso atualizar o artigo.

Boa codificação!

Fechamentos de JavaScript são usados ​​para ocultar os valores das variáveis ​​e armazenar os valores das funções. O resultado final é que, ao fechar, é criada uma função na qual as variáveis ​​são definidas e que, como resultado de seu trabalho, retorna sua função aninhada. Em seguida, uma função aninhada é criada nela (na função principal), na qual algumas operações são feitas com as variáveis ​​da função principal e que retorna o resultado dessas operações. Além disso, a função principal é equiparada a algum tipo de variável - esta variável pode ser chamada qualquer número de vezes e ao mesmo tempo os valores das variáveis ​​da função principal serão armazenados e atualizados nela. está fechado".

Como você sabe, em JavaScript, o escopo das variáveis ​​locais é (declarado com a palavra var)é o corpo da função dentro da qual são definidos.

Se você declarar uma função dentro de outra função, a primeira acessa as variáveis ​​e os argumentos da segunda:

Código: function outerFn (myArg) (
var myVar;
função innerFn () (
// tem acesso a myVar e myArg
}
}

Ao mesmo tempo, tais variáveis ​​continuam a existir e permanecem disponíveis para a função interna mesmo após a função externa na qual estão definidas ter sido executada.

Vamos considerar um exemplo - uma função que retorna o número de suas próprias chamadas:

Código: function createCounter () (
var numberOfCalls = 0;
função de retorno () (
return ++ numberOfCalls;
}
}
var fn = createCounter ();
fn (); // 1
fn (); // 2
fn (); // 3

Neste exemplo, a função retornada por createCounter usa a variável numberOfCalls, que retém o valor desejado entre suas chamadas (em vez de deixar de existir imediatamente com o retorno de createCounter).

É por essas propriedades que tais funções "aninhadas" em JavaScript são chamadas de closures (um termo que vem de linguagens de programação funcionais) - elas "fecham" as variáveis ​​e argumentos da função dentro da qual são definidas.

Aplicando fechamentos

Vamos simplificar um pouco o exemplo acima - removeremos a necessidade de chamar separadamente a função createCounter, tornando-a anônima e chamando-a imediatamente após sua declaração:

Código: var fn = (function () (
var numberOfCalls = 0;
função de retorno () (
return ++ numberOfCalls;
}
})();

Esse design nos permitiu vincular dados a uma função que persiste entre suas chamadas - esse é um dos usos dos encerramentos. Em outras palavras, com a ajuda deles podemos criar funções que têm seu próprio estado mutável.

Outro bom uso dos encerramentos é criar funções que, por sua vez, também criam funções - o que alguns chamariam de truque. metaprogramação.
Por exemplo:

Código: var createHelloFunction = function (name) (
função de retorno () (
alert ("Olá," + nome);
}
}
var sayHelloHabrahabr = createHelloFunction ("Habrahabr");
digaHelloHabrahabr (); // alerta "Olá, Habrahabr"

Graças ao encerramento, a função retornada "lembra" os parâmetros passados ​​para a função que cria, que é o que precisamos para esse tipo de coisa.

Uma situação semelhante surge quando não retornamos a função interna, mas a penduramos em algum evento - uma vez que o evento ocorre depois que a função foi executada, o fechamento novamente ajuda a não perder os dados transferidos durante a criação do manipulador.

Vejamos um exemplo um pouco mais complexo - um método que vincula uma função a um contexto específico (ou seja, o objeto para o qual a palavra this irá apontar nele).

Código: Function.prototype.bind = function (context) (
var fn = this;
função de retorno () (
return fn.apply (contexto, argumentos);
};
}
var HelloPage = (
nome: "Habrahabr",
init: function () (
alert ("Olá," + this.name);
}
}
//window.onload = HelloPage.init; // alertaria indefinido como isso apontaria para a janela
window.onload = HelloPage.init.bind (HelloPage); // agora tudo funciona

Neste exemplo, usando fechamentos, a função retornada por bind lembra a função inicial e o contexto atribuído a ela.

O próximo uso fundamentalmente diferente de encerramentos é a proteção de dados (encapsulamento)... Considere a seguinte construção:

Código: (função () (

})();

Obviamente, dentro do encerramento temos acesso a todos os dados externos, mas ao mesmo tempo ele possui os seus próprios. Graças a isso, podemos envolver partes do código com uma construção semelhante para fechar as variáveis ​​locais que entraram no acesso de fora. (Você pode ver um exemplo de seu uso no código-fonte da biblioteca jQuery, que envolve todo o seu código com um encerramento para não expor as variáveis ​​de que precisa apenas fora dele).

Há, no entanto, uma armadilha associada a esse uso - dentro da tampa, o significado da palavra this se perde fora dela. É resolvido da seguinte forma:

Código: (função () (
// o superior será salvo
)). call (this);

Vamos considerar outro truque da mesma série. Foi amplamente popularizado pelos desenvolvedores da estrutura de interface do usuário do Yahoo, chamando-o de "Padrão de Módulo" e escrevendo um artigo inteiro sobre isso no blog oficial.
Vamos ter um objeto (singleton) contendo quaisquer métodos e propriedades:

Código: var MyModule = (
nome: "Habrahabr",
sayPreved: function (name) (
alert ("PREVED" + name.toUpperCase ())
},
this.sayPreved (this.name);
}
}
MyModule.sayPrevedToHabrahabr ();

Com a ajuda de um fechamento, podemos tornar privados os métodos e propriedades que não são usados ​​fora do objeto. (ou seja, acessível apenas para ele):

Código: var MyModule = (function () (
var nome = "Habrahabr";
função sayPreved () (
alert ("PREVED" + name.toUpperCase ());
}
Retorna (
sayPrevedToHabrahabr: function () (
sayPreved (nome);
}
}
})();
MyModule.sayPrevedToHabrahabr (); // alerta "PREVED Habrahabr"

Por fim, desejo descrever um erro comum que leva muitos ao estupor se não souberem como funcionam os fechamentos.

Suponha que temos uma série de links e nossa tarefa é garantir que, quando cada um for clicado, seu número de série seja exibido com um alerta.
A primeira solução que vem à mente é a seguinte:

Código: para (var i = 0; i< links.length; i++) {
alerta (i);
}
}

Na verdade, quando você clica em qualquer link, o mesmo número é exibido - o valor de links.length. Por que isso acontece? Em conexão com o encerramento, a variável auxiliar declarada i continua a existir, mesmo no momento em que clicamos no link. Como nessa altura o ciclo já passou, eu continuo igual ao número de links - este é o valor que vemos quando clicamos.

Este problema é resolvido da seguinte forma:

Código: para (var i = 0; i< links.length; i++) {
(função (i) (
links [i] .onclick = function () (
alerta (i);
}
)) (eu);
}

Aqui, com a ajuda de outro encerramento, "sombreamos" a variável i, criando uma cópia dela em seu escopo local a cada etapa do loop. Graças a isso, tudo agora funciona como planejado.

Isso é tudo. Este artigo, é claro, não pretende ser exaustivo, mas, espero, ainda ajudará alguém a descobri-lo.

shl
Para salvar entre chamadas, é mais fácil usar func_name.attr como:

Código: função countIt (reset) (
if (reset ||! countIt.cnt) countIt.cnt = 0;
return countIt.cnt ++;