De vez en cuando veo que se mencionan los "cierres", e intenté buscarlo, pero Wiki no da una explicación que entiendo. ¿Podría alguien ayudarme aquí?
De vez en cuando veo que se mencionan los "cierres", e intenté buscarlo, pero Wiki no da una explicación que entiendo. ¿Podría alguien ayudarme aquí?
Respuestas:
(Descargo de responsabilidad: esta es una explicación básica; en lo que respecta a la definición, estoy simplificando un poco)
La forma más simple de pensar en un cierre es una función que se puede almacenar como una variable (denominada "función de primera clase"), que tiene una capacidad especial para acceder a otras variables locales en el ámbito en el que se creó.
Ejemplo (JavaScript):
var setKeyPress = function(callback) {
document.onkeypress = callback;
};
var initialize = function() {
var black = false;
document.onclick = function() {
black = !black;
document.body.style.backgroundColor = black ? "#000000" : "transparent";
}
var displayValOfBlack = function() {
alert(black);
}
setKeyPress(displayValOfBlack);
};
initialize();
Las funciones 1 asignadas document.onclick
y displayValOfBlack
son cierres. Puede ver que ambos hacen referencia a la variable booleana black
, pero esa variable se asigna fuera de la función. Como black
es local en el ámbito donde se definió la función , se conserva el puntero a esta variable.
Si pones esto en una página HTML:
Esto demuestra que ambos tienen acceso al mismo black
y pueden usarse para almacenar el estado sin ningún objeto contenedor.
La llamada a setKeyPress
es demostrar cómo se puede pasar una función como cualquier variable. El alcance conservado en el cierre sigue siendo aquel en el que se definió la función.
Los cierres se usan comúnmente como controladores de eventos, especialmente en JavaScript y ActionScript. El buen uso de los cierres lo ayudará a vincular implícitamente las variables a los controladores de eventos sin tener que crear un contenedor de objetos. Sin embargo, el uso descuidado conducirá a pérdidas de memoria (como cuando un controlador de eventos no utilizado pero preservado es lo único que puede retener objetos grandes en la memoria, especialmente objetos DOM, evitando la recolección de basura).
1: En realidad, todas las funciones en JavaScript son cierres.
black
se declara dentro de una función, ¿no se destruiría eso a medida que la pila se desenrolla ...?
black
se declara dentro de una función, eso no se destruiría". Recuerde también que si declara un objeto en una función y luego lo asigna a una variable que vive en otro lugar, ese objeto se conserva porque hay otras referencias a él.
Un cierre es básicamente una forma diferente de mirar un objeto. Un objeto son datos que tienen una o más funciones vinculadas. Un cierre es una función que tiene una o más variables vinculadas. Los dos son básicamente idénticos, al menos en un nivel de implementación. La verdadera diferencia está en de dónde vienen.
En la programación orientada a objetos, declara una clase de objeto definiendo sus variables miembro y sus métodos (funciones miembro) por adelantado, y luego crea instancias de esa clase. Cada instancia viene con una copia de los datos del miembro, inicializada por el constructor. Luego tiene una variable de un tipo de objeto y la pasa como un dato, porque el foco está en su naturaleza como dato.
En un cierre, por otro lado, el objeto no se define por adelantado como una clase de objeto, ni se instancia a través de una llamada de constructor en su código. En cambio, escribe el cierre como una función dentro de otra función. El cierre puede referirse a cualquiera de las variables locales de la función externa, y el compilador lo detecta y mueve estas variables desde el espacio de la pila de la función externa a la declaración de objeto oculto del cierre. Luego tiene una variable de tipo de cierre, y aunque es básicamente un objeto debajo del capó, la pasa como referencia de función, porque el foco está en su naturaleza como función.
El término cierre proviene del hecho de que un fragmento de código (bloque, función) puede tener variables libres que están cerradas (es decir, vinculadas a un valor) por el entorno en el que se define el bloque de código.
Tomemos, por ejemplo, la definición de la función Scala:
def addConstant(v: Int): Int = v + k
En el cuerpo de la función hay dos nombres (variables) v
y k
que indican dos valores enteros. El nombre v
está enlazado porque se declara como un argumento de la función addConstant
(al observar la declaración de la función sabemos que v
se le asignará un valor cuando se invoque la función). El nombre k
es libre de la función addConstant
porque la función no contiene ninguna pista sobre qué valor k
está vinculado (y cómo).
Para evaluar una llamada como:
val n = addConstant(10)
tenemos que asignar k
un valor, que solo puede suceder si el nombre k
se define en el contexto en el que addConstant
se define. Por ejemplo:
def increaseAll(values: List[Int]): List[Int] =
{
val k = 2
def addConstant(v: Int): Int = v + k
values.map(addConstant)
}
Ahora que lo hemos definido addConstant
en un contexto donde k
está definido, se addConstant
ha convertido en un cierre porque todas sus variables libres ahora están cerradas (vinculadas a un valor): addConstant
pueden invocarse y pasarse como si fuera una función. Tenga en cuenta que la variable libre k
está vinculada a un valor cuando se define el cierre , mientras que la variable de argumento v
está vinculada cuando se invoca el cierre .
Por lo tanto, un cierre es básicamente una función o un bloque de código que puede acceder a valores no locales a través de sus variables libres después de que estos hayan sido vinculados por el contexto.
En muchos idiomas, si usa un cierre solo una vez, puede hacerlo anónimo , p. Ej.
def increaseAll(values: List[Int]): List[Int] =
{
val k = 2
values.map(v => v + k)
}
Tenga en cuenta que una función sin variables libres es un caso especial de cierre (con un conjunto vacío de variables libres). Análogamente, una función anónima es un caso especial de un cierre anónimo , es decir, una función anónima es un cierre anónimo sin variables libres.
Una explicación simple en JavaScript:
var closure_example = function() {
var closure = 0;
// after first iteration the value will not be erased from the memory
// because it is bound with the returned alertValue function.
return {
alertValue : function() {
closure++;
alert(closure);
}
};
};
closure_example();
alert(closure)
utilizará el valor creado previamente de closure
. El alertValue
espacio de nombres de la función devuelta se conectará al espacio de nombres en el que closure
reside la variable. Cuando elimine la función completa, el valor de la closure
variable se eliminará, pero hasta entonces, la alertValue
función siempre podrá leer / escribir el valor de la variable closure
.
Si ejecuta este código, la primera iteración asignará un valor 0 a la closure
variable y reescribirá la función para:
var closure_example = function(){
alertValue : function(){
closure++;
alert(closure);
}
}
Y debido a que alertValue
necesita la variable local closure
para ejecutar la función, se vincula con el valor de la variable local previamente asignada closure
.
Y ahora cada vez que llame a la closure_example
función, escribirá el valor incrementado de la closure
variable porque alert(closure)
está enlazado.
closure_example.alertValue()//alerts value 1
closure_example.alertValue()//alerts value 2
closure_example.alertValue()//alerts value 3
//etc.
Un "cierre" es, en esencia, un estado local y un código, combinados en un paquete. Por lo general, el estado local proviene de un ámbito circundante (léxico) y el código es (esencialmente) una función interna que luego se devuelve al exterior. El cierre es entonces una combinación de las variables capturadas que ve la función interna y el código de la función interna.
Es una de esas cosas que, desafortunadamente, es un poco difícil de explicar, debido a que no está familiarizado.
Una analogía que utilicé con éxito en el pasado fue "imagina que tenemos algo que llamamos 'el libro', en el cierre de la habitación, 'el libro' es esa copia allí, en la esquina, de TAOCP, pero en el cierre de la mesa , es esa copia de un libro de Dresden Files. Entonces, dependiendo del cierre en el que se encuentre, el código 'dame el libro' da como resultado que sucedan diferentes cosas ".
static
variable local considerarse un cierre? ¿Los cierres en Haskell involucran al estado?
static
variable local, tiene exactamente uno).
Es difícil definir qué es el cierre sin definir el concepto de "estado".
Básicamente, en un lenguaje con alcance léxico completo que trata las funciones como valores de primera clase, sucede algo especial. Si tuviera que hacer algo como:
function foo(x)
return x
end
x = foo
La variable x
no solo hace referencia function foo()
sino que también hace referencia al estado que foo
se dejó la última vez que regresó. La verdadera magia ocurre cuando foo
tiene otras funciones más definidas dentro de su alcance; es como su propio mini-entorno (tal como 'normalmente' definimos funciones en un entorno global).
Funcionalmente, puede resolver muchos de los mismos problemas que la palabra clave 'estática' de C ++ (C?), Que retiene el estado de una variable local a través de múltiples llamadas de función; sin embargo, es más como aplicar ese mismo principio (variable estática) a una función, ya que las funciones son valores de primera clase; el cierre agrega soporte para que se guarde todo el estado de la función (nada que ver con las funciones estáticas de C ++).
Tratar las funciones como valores de primera clase y agregar soporte para cierres también significa que puede tener más de una instancia de la misma función en la memoria (similar a las clases). Lo que esto significa es que puede reutilizar el mismo código sin tener que restablecer el estado de la función, como se requiere cuando se trata con variables estáticas de C ++ dentro de una función (¿puede estar equivocado sobre esto?).
Aquí hay algunas pruebas del soporte de cierre de Lua.
--Closure testing
--By Trae Barlow
--
function myclosure()
print(pvalue)--nil
local pvalue = pvalue or 10
return function()
pvalue = pvalue + 10 --20, 31, 42, 53(53 never printed)
print(pvalue)
pvalue = pvalue + 1 --21, 32, 43(pvalue state saved through multiple calls)
return pvalue
end
end
x = myclosure() --x now references anonymous function inside myclosure()
x()--nil, 20
x() --21, 31
x() --32, 42
--43, 53 -- if we iterated x() again
resultados:
nil
20
31
42
Puede ser complicado, y probablemente varía de un idioma a otro, pero en Lua parece que cada vez que se ejecuta una función, se restablece su estado. Digo esto porque los resultados del código anterior serían diferentes si estuviéramos accediendo a la myclosure
función / estado directamente (en lugar de a través de la función anónima que devuelve), ya pvalue
que se restablecería a 10; pero si accedemos al estado de myclosure a través de x (la función anónima) puede ver que pvalue
está vivo y bien en algún lugar de la memoria. Sospecho que hay un poco más, tal vez alguien pueda explicar mejor la naturaleza de la implementación.
PD: No conozco ni una pizca de C ++ 11 (aparte de lo que hay en versiones anteriores), así que tenga en cuenta que esto no es una comparación entre los cierres en C ++ 11 y Lua. Además, todas las 'líneas dibujadas' de Lua a C ++ son similitudes ya que las variables estáticas y los cierres no son 100% iguales; incluso si a veces se usan para resolver problemas similares.
Lo que no estoy seguro es, en el ejemplo de código anterior, si la función anónima o la función de orden superior se considera el cierre.
Un cierre es una función que tiene un estado asociado:
En perl creas cierres como este:
#!/usr/bin/perl
# This function creates a closure.
sub getHelloPrint
{
# Bind state for the function we are returning.
my ($first) = @_;a
# The function returned will have access to the variable $first
return sub { my ($second) = @_; print "$first $second\n"; };
}
my $hw = getHelloPrint("Hello");
my $gw = getHelloPrint("Goodby");
&$hw("World"); // Print Hello World
&$gw("World"); // PRint Goodby World
Si nos fijamos en la nueva funcionalidad proporcionada con C ++.
También le permite vincular el estado actual al objeto:
#include <string>
#include <iostream>
#include <functional>
std::function<void(std::string const&)> getLambda(std::string const& first)
{
// Here we bind `first` to the function
// The second parameter will be passed when we call the function
return [first](std::string const& second) -> void
{ std::cout << first << " " << second << "\n";
};
}
int main(int argc, char* argv[])
{
auto hw = getLambda("Hello");
auto gw = getLambda("GoodBye");
hw("World");
gw("World");
}
Consideremos una función simple:
function f1(x) {
// ... something
}
Esta función se llama función de nivel superior porque no está anidada en ninguna otra función. Cada función de JavaScript asocia consigo una lista de objetos llamada "Cadena de alcance" . Esta cadena de alcance es una lista ordenada de objetos. Cada uno de estos objetos define algunas variables.
En las funciones de nivel superior, la cadena de alcance consta de un solo objeto, el objeto global. Por ejemplo, la función f1
anterior tiene una cadena de alcance que tiene un solo objeto que define todas las variables globales. (tenga en cuenta que el término "objeto" aquí no significa objeto JavaScript, es solo un objeto definido por la implementación que actúa como un contenedor de variables, en el que JavaScript puede "buscar" variables).
Cuando se invoca esta función, JavaScript crea algo llamado "objeto de activación" y lo coloca en la parte superior de la cadena de alcance. Este objeto contiene todas las variables locales (por ejemplo, x
aquí). Por lo tanto, ahora tenemos dos objetos en la cadena de alcance: el primero es el objeto de activación y debajo está el objeto global.
Tenga en cuenta con mucho cuidado que los dos objetos se colocan en la cadena del osciloscopio en DIFERENTES veces. El objeto global se coloca cuando se define la función (es decir, cuando JavaScript analiza la función y crea el objeto de la función), y el objeto de activación entra cuando se invoca la función.
Entonces, ahora sabemos esto:
La situación se pone interesante cuando tratamos con funciones anidadas. Entonces, creemos uno:
function f1(x) {
function f2(y) {
// ... something
}
}
Cuando f1
se define, obtenemos una cadena de alcance que contiene solo el objeto global.
Ahora cuando f1
se llama, la cadena de alcance de f1
obtiene el objeto de activación. Este objeto de activación contiene la variable x
y la variable f2
que es una función. Y, tenga en cuenta que f2
se está definiendo. Por lo tanto, en este punto, JavaScript también guarda una nueva cadena de alcance para f2
. La cadena de alcance guardada para esta función interna es la cadena de alcance actual vigente. La cadena de alcance actual en efecto es la de f1
's. Por lo tanto f2
, la cadena de alcance es f1
la cadena de alcance actual , que contiene el objeto de activación f1
y el objeto global.
Cuando f2
se llama, obtiene su propio objeto de activación que contiene y
, agregado a su cadena de alcance que ya contiene el objeto de activación f1
y el objeto global.
Si hubiera otra función anidada definida dentro f2
, su cadena de alcance contendría tres objetos en el momento de la definición (2 objetos de activación de dos funciones externas y el objeto global) y 4 en el momento de la invocación.
Entonces, ahora entendemos cómo funciona la cadena de alcance, pero aún no hemos hablado de cierres.
La combinación de un objeto de función y un ámbito (un conjunto de enlaces de variables) en el que se resuelven las variables de la función se denomina cierre en la literatura de informática - JavaScript, la guía definitiva de David Flanagan
La mayoría de las funciones se invocan usando la misma cadena de alcance que estaba vigente cuando se definió la función, y realmente no importa que haya un cierre involucrado. Los cierres se vuelven interesantes cuando se invocan bajo una cadena de alcance diferente a la que estaba vigente cuando se definieron. Esto ocurre más comúnmente cuando un objeto de función anidada se devuelve desde la función dentro de la cual se definió.
Cuando la función regresa, ese objeto de activación se elimina de la cadena de alcance. Si no había funciones anidadas, no hay más referencias al objeto de activación y se recolecta basura. Si se definieron funciones anidadas, cada una de esas funciones tiene una referencia a la cadena de alcance, y esa cadena de alcance se refiere al objeto de activación.
Sin embargo, si esos objetos de funciones anidadas permanecieron dentro de su función externa, entonces ellos mismos serán recolectados de basura, junto con el objeto de activación al que se refirieron. Pero si la función define una función anidada y la devuelve o la almacena en una propiedad en algún lugar, entonces habrá una referencia externa a la función anidada. No se recolectará basura, y el objeto de activación al que hace referencia tampoco se recolectará basura.
En nuestro ejemplo anterior, no regresamos f2
de f1
, por lo tanto, cuando una llamada a f1
retorna, su objeto de activación se eliminará de su cadena de alcance y se recolectará la basura. Pero si tuviéramos algo como esto:
function f1(x) {
function f2(y) {
// ... something
}
return f2;
}
Aquí, la devolución f2
tendrá una cadena de alcance que contendrá el objeto de activación f1
y, por lo tanto, no se recolectará basura. En este punto, si llamamos f2
, podrá acceder a f1
la variable x
aunque estemos fuera de f1
.
Por lo tanto, podemos ver que una función mantiene su cadena de alcance con ella y con la cadena de alcance vienen todos los objetos de activación de funciones externas. Esta es la esencia del cierre. Decimos que las funciones en JavaScript tienen "ámbito léxico" , lo que significa que guardan el ámbito que estaba activo cuando se definieron en lugar del ámbito que estaba activo cuando se les llamaba.
Hay una serie de potentes técnicas de programación que implican cierres como aproximación de variables privadas, programación dirigida por eventos, aplicación parcial , etc.
También tenga en cuenta que todo esto se aplica a todos los idiomas que admiten cierres. Por ejemplo PHP (5.3+), Python, Ruby, etc.
Un cierre es una optimización del compilador (también conocido como azúcar sintáctico?). Algunas personas también se han referido a esto como el Objeto del Pobre .
Ver la respuesta de Eric Lippert : (extracto a continuación)
El compilador generará código como este:
private class Locals
{
public int count;
public void Anonymous()
{
this.count++;
}
}
public Action Counter()
{
Locals locals = new Locals();
locals.count = 0;
Action counter = new Action(locals.Anonymous);
return counter;
}
¿Tener sentido?
Además, solicitó comparaciones. VB y JScript crean cierres de la misma manera.