De vez em quando eu vejo "fechamentos" sendo mencionados, e tentei procurar, mas o Wiki não dá uma explicação que eu entenda. Alguém poderia me ajudar aqui?
language-features
closures
gablin
fonte
fonte
Respostas:
(Isenção de responsabilidade: esta é uma explicação básica; no que diz respeito à definição, estou simplificando um pouco)
A maneira mais simples de pensar em um fechamento é uma função que pode ser armazenada como uma variável (denominada "função de primeira classe"), que possui uma capacidade especial de acessar outras variáveis locais do escopo em que foi criado.
Exemplo (JavaScript):
As funções 1 atribuídas
document.onclick
edisplayValOfBlack
são encerramentos. Você pode ver que os dois referenciam a variável booleanablack
, mas essa variável é atribuída fora da função. Comoblack
é local no escopo em que a função foi definida , o ponteiro para essa variável é preservado.Se você colocar isso em uma página HTML:
Isso demonstra que ambos têm acesso ao mesmo
black
e podem ser usados para armazenar o estado sem nenhum objeto de wrapper.A chamada para
setKeyPress
é demonstrar como uma função pode ser passada como qualquer variável. O escopo preservado no fechamento ainda é aquele em que a função foi definida.Os fechamentos são comumente usados como manipuladores de eventos, especialmente em JavaScript e ActionScript. O bom uso de encerramentos ajudará você a vincular implicitamente variáveis a manipuladores de eventos sem precisar criar um wrapper de objeto. No entanto, o uso descuidado levará a vazamentos de memória (como quando um manipulador de eventos não utilizado, mas preservado, é a única coisa a manter objetos grandes na memória, especialmente objetos DOM, impedindo a coleta de lixo).
1: Na verdade, todas as funções em JavaScript são encerramentos.
fonte
black
é declarado dentro de uma função, isso não seria destruído à medida que a pilha se desenrola ...?black
é declarado dentro de uma função, isso não seria destruído". Lembre-se também de que, se você declarar um objeto em uma função e depois atribuí-lo a uma variável que mora em outro lugar, esse objeto será preservado porque há outras referências a ele.Um fechamento é basicamente apenas uma maneira diferente de olhar para um objeto. Um objeto são dados que possuem uma ou mais funções vinculadas a ele. Um fechamento é uma função que possui uma ou mais variáveis ligadas a ele. Os dois são basicamente idênticos, pelo menos em um nível de implementação. A verdadeira diferença está em onde eles vêm.
Na programação orientada a objetos, você declara uma classe de objeto definindo suas variáveis de membro e seus métodos (funções de membro) antecipadamente e, em seguida, cria instâncias dessa classe. Cada instância vem com uma cópia dos dados do membro, inicializada pelo construtor. Você então tem uma variável de um tipo de objeto e a transmite como um dado, porque o foco é sua natureza como dados.
Em um fechamento, por outro lado, o objeto não é definido antecipadamente como uma classe de objeto ou instanciado por meio de uma chamada de construtor em seu código. Em vez disso, você escreve o fechamento como uma função dentro de outra função. O fechamento pode se referir a qualquer variável local da função externa e o compilador detecta isso e move essas variáveis do espaço de pilha da função externa para a declaração de objeto oculto do fechamento. Você então tem uma variável de um tipo de fechamento e, embora seja basicamente um objeto sob o capô, transmite-o como uma referência de função, porque o foco é sua natureza como uma função.
fonte
O termo encerramento vem do fato de que um trecho de código (bloco, função) pode ter variáveis livres que são fechadas (ou seja, vinculadas a um valor) pelo ambiente em que o bloco de código está definido.
Tomemos, por exemplo, a definição da função Scala:
No corpo da função, existem dois nomes (variáveis)
v
ek
indicam dois valores inteiros. O nomev
é vinculado porque é declarado como um argumento da funçãoaddConstant
(observando a declaração da função, sabemos quev
será atribuído um valor quando a função for chamada). O nomek
é livre para a funçãoaddConstant
porque a função não contém nenhuma pista sobre a qual valork
está vinculado (e como).Para avaliar uma chamada como:
temos que atribuir
k
um valor, o que só pode acontecer se o nomek
estiver definido no contexto em queaddConstant
está definido. Por exemplo:Agora que definimos
addConstant
em um contexto em quek
é definido,addConstant
tornou-se um fechamento porque todas as suas variáveis livres agora estão fechadas (vinculadas a um valor):addConstant
podem ser invocadas e passadas como se fosse uma função. Observe que a variável livrek
é vinculada a um valor quando o fechamento é definido , enquanto a variável de argumentov
é vinculada quando o fechamento é invocado .Portanto, um fechamento é basicamente uma função ou bloco de código que pode acessar valores não locais por meio de suas variáveis livres depois que elas são vinculadas pelo contexto.
Em muitos idiomas, se você usar um fechamento apenas uma vez, poderá torná-lo anônimo , por exemplo
Observe que uma função sem variáveis livres é um caso especial de fechamento (com um conjunto vazio de variáveis livres). Analogamente, uma função anônima é um caso especial de fechamento anônimo , ou seja, uma função anônima é um fechamento anônimo sem variáveis livres.
fonte
Uma explicação simples em JavaScript:
alert(closure)
usará o valor criado anteriormente declosure
. OalertValue
espaço de nome da função retornada será conectado ao espaço de nome no qual aclosure
variável reside. Quando você exclui toda a função, o valor daclosure
variável será excluído, mas até então, aalertValue
função sempre poderá ler / gravar o valor da variávelclosure
.Se você executar esse código, a primeira iteração atribuirá um valor 0 à
closure
variável e reescreverá a função para:E porque
alertValue
precisa da variável localclosure
para executar a função, ela se liga ao valor da variável local atribuída anteriormenteclosure
.E agora, toda vez que você chama a
closure_example
função, ela grava o valor incrementado daclosure
variável porquealert(closure)
está vinculada.fonte
Um "fechamento" é, em essência, algum estado local e algum código, combinados em um pacote. Normalmente, o estado local vem de um escopo circundante (lexical) e o código é (essencialmente) uma função interna que é retornada para fora. O fechamento é então uma combinação das variáveis capturadas que a função interna vê e o código da função interna.
Infelizmente, é uma daquelas coisas que é um pouco difícil de explicar, por não ser familiar.
Uma analogia que usei com sucesso no passado foi "imagine que temos algo que chamamos de 'livro', no fechamento da sala, 'o livro' é aquela cópia ali, no canto, do TAOCP, mas no fechamento da mesa , é a cópia de um livro do Dresden Files. Portanto, dependendo de qual encerramento você está, o código 'me dê o livro' resulta em diferentes coisas acontecendo ".
fonte
static
variável local pode ser considerada um fechamento? Os fechamentos em Haskell envolvem estado?static
variável local, você tem exatamente um).É difícil definir o que é o fechamento sem definir o conceito de "estado".
Basicamente, em uma linguagem com escopo lexical completo que trata as funções como valores de primeira classe, algo especial acontece. Se eu fizesse algo como:
A variável
x
não apenas faz referência,function foo()
mas também faz referência ao estado quefoo
foi deixado na última vez que retornou. A mágica real acontece quandofoo
há outras funções definidas mais dentro de seu escopo; é como seu próprio mini-ambiente (assim como 'normalmente' definimos funções em um ambiente global).Funcionalmente, ele pode resolver muitos dos mesmos problemas que a palavra-chave 'estática' do C ++ (C?), Que retém o estado de uma variável local através de várias chamadas de função; no entanto, é mais como aplicar o mesmo princípio (variável estática) a uma função, pois as funções são valores de primeira classe; O encerramento adiciona suporte para que todo o estado da função seja salvo (nada a ver com as funções estáticas do C ++).
Tratar funções como valores de primeira classe e adicionar suporte a fechamentos também significa que você pode ter mais de uma instância da mesma função na memória (semelhante às classes). O que isso significa é que você pode reutilizar o mesmo código sem precisar redefinir o estado da função, conforme é necessário ao lidar com variáveis estáticas do C ++ dentro de uma função (pode estar errado sobre isso?).
Aqui estão alguns testes do suporte ao fechamento de Lua.
resultados:
Pode ser complicado e provavelmente varia de idioma para idioma, mas parece em Lua que sempre que uma função é executada, seu estado é redefinido. Digo isso porque os resultados do código acima seriam diferentes se estivéssemos acessando a
myclosure
função / estado diretamente (em vez de retornar pela função anônima), poispvalue
seria redefinido para 10; mas se acessarmos o estado do myclosure por meio de x (a função anônima), você poderá ver que elepvalue
está vivo e bom em algum lugar da memória. Suspeito que exista um pouco mais, talvez alguém possa explicar melhor a natureza da implementação.PS: Eu não conheço uma lambida de C ++ 11 (além do que está nas versões anteriores), portanto, observe que essa não é uma comparação entre fechamentos em C ++ 11 e Lua. Além disso, todas as 'linhas desenhadas' de Lua para C ++ são semelhanças, pois variáveis estáticas e fechamentos não são 100% iguais; mesmo que sejam usados algumas vezes para resolver problemas semelhantes.
O que eu não tenho certeza é, no exemplo de código acima, se a função anônima ou a função de ordem superior é considerada o fechamento?
fonte
Um fechamento é uma função que tem estado associado:
No perl, você cria fechamentos como este:
Se olharmos para a nova funcionalidade fornecida com o C ++.
Também permite vincular o estado atual ao objeto:
fonte
Vamos considerar uma função simples:
Essa função é chamada de função de nível superior porque não está aninhada em nenhuma outra função. Toda função JavaScript associa consigo uma lista de objetos chamada "Cadeia de escopo" . Essa cadeia de escopo é uma lista ordenada de objetos. Cada um desses objetos define algumas variáveis.
Nas funções de nível superior, a cadeia de escopo consiste em um único objeto, o objeto global. Por exemplo, a função
f1
acima possui uma cadeia de escopo que possui um único objeto que define todas as variáveis globais. (observe que o termo "objeto" aqui não significa objeto JavaScript, é apenas um objeto definido para implementação que atua como um contêiner de variável, no qual o JavaScript pode "procurar" variáveis.)Quando essa função é chamada, o JavaScript cria algo chamado "objeto de ativação" e o coloca no topo da cadeia de escopo. Este objeto contém todas as variáveis locais (por exemplo,
x
aqui). Portanto, agora temos dois objetos na cadeia de escopo: o primeiro é o objeto de ativação e, abaixo dele, o objeto global.Observe com muito cuidado que os dois objetos são colocados na cadeia de escopo em momentos DIFERENTES. O objeto global é colocado quando a função é definida (ou seja, quando o JavaScript analisa a função e cria o objeto da função) e o objeto de ativação entra quando a função é chamada.
Então, agora sabemos isso:
A situação fica interessante quando lidamos com funções aninhadas. Então, vamos criar um:
Quando
f1
definido, obtemos uma cadeia de escopo contendo apenas o objeto global.Agora, quando
f1
é chamado, a cadeia de escopof1
recebe o objeto de ativação. Este objeto de ativação contém a variávelx
e a variávelf2
que é uma função. E observe quef2
está sendo definido. Portanto, neste ponto, o JavaScript também salva uma nova cadeia de escopof2
. A cadeia de escopo salva para esta função interna é a atual cadeia de escopo em vigor. A cadeia de escopo atual em vigor é a def1
's. Portantof2
, a cadeia de escopo éf1
a cadeia de escopo atual - que contém o objeto de ativaçãof1
e o objeto global.Quando
f2
é chamado, ele obtém seu próprio objeto de ativaçãoy
, adicionado à sua cadeia de escopo, que já contém o objeto de ativaçãof1
e o objeto global.Se houvesse outra função aninhada definida dentro
f2
, sua cadeia de escopo conteria três objetos no momento da definição (2 objetos de ativação de duas funções externas e o objeto global) e 4 no momento da chamada.Então, agora entendemos como a cadeia de escopo funciona, mas ainda não falamos sobre fechamentos.
A maioria das funções é chamada usando a mesma cadeia de escopo que estava em vigor quando a função foi definida, e não importa realmente se há um fechamento envolvido. Os fechamentos tornam-se interessantes quando são chamados sob uma cadeia de escopo diferente daquela que estava em vigor quando foram definidos. Isso acontece mais comumente quando um objeto de função aninhada é retornado da função na qual ele foi definido.
Quando a função retorna, esse objeto de ativação é removido da cadeia de escopo. Se não houver funções aninhadas, não haverá mais referências ao objeto de ativação e ele será coletado como lixo. Se houvesse funções aninhadas definidas, cada uma dessas funções terá uma referência à cadeia de escopo, e essa cadeia de escopo se refere ao objeto de ativação.
Se esses objetos de funções aninhadas permanecerem dentro de sua função externa, eles mesmos serão coletados como lixo, juntamente com o objeto de ativação a que se referiram. Mas se a função define uma função aninhada e a retorna ou a armazena em uma propriedade em algum lugar, haverá uma referência externa à função aninhada. Ele não será coletado como lixo e o objeto de ativação a que se refere também não será coletado.
No nosso exemplo, acima, não voltar
f2
a partir def1
, por isso, quando uma chamada def1
retorno, o seu objecto de activação será removido a partir do seu âmbito e cadeia de lixo recolhido. Mas se tivéssemos algo assim:Aqui, o retorno
f2
terá uma cadeia de escopo que conterá o objeto de ativação def1
e, portanto, não será coletado como lixo. Nesse ponto, se chamarmosf2
, ele poderá acessarf1
a variável dex
mesmo que estejamos semf1
.Portanto, podemos ver que uma função mantém sua cadeia de escopo com ela e com a cadeia de escopo vêm todos os objetos de ativação de funções externas. Essa é a essência do fechamento. Dizemos que as funções no JavaScript têm "escopo lexical " , o que significa que elas salvam o escopo que estava ativo quando elas foram definidas, em oposição ao escopo que estava ativo quando foram chamadas.
Existem várias técnicas de programação poderosas que envolvem fechamentos, como aproximar variáveis privadas, programação orientada a eventos, aplicação parcial etc.
Observe também que tudo isso se aplica a todos os idiomas que suportam fechamentos. Por exemplo, PHP (5.3+), Python, Ruby, etc.
fonte
Um fechamento é uma otimização de compilador (também conhecido como açúcar sintático?). Algumas pessoas também se referiram a isso como o objeto do pobre homem .
Veja a resposta de Eric Lippert : (trecho abaixo)
O compilador irá gerar código como este:
Faz sentido?
Além disso, você pediu comparações. O VB e o JScript criam fechamentos praticamente da mesma maneira.
fonte