De vez en cuando veo que se mencionan «cierres», y traté de buscarlo, pero Wiki no da una explicación que yo entienda. ¿Podría alguien Ayúdame aquí?
Comentarios
- Si conoces Java / C # espero que este enlace te ayude- http://www.developerfusion.com/article/8251/the-beauty-of-closures/
- Los cierres son difíciles de entender. Debes intentar hacer clic en todos los enlaces de la primera oración de ese artículo de Wikipedia y comprenderlos artículos primero.
- stackoverflow.com/questions/36636/what-is-a-closure
- Qué ‘ s la diferencia fundamental entre un cierre y una clase? Está bien, una clase con un solo método público.
- @biziclop: Tú podrías emular un cierre con una clase (que ‘ es lo que tienen que hacer los desarrolladores de Java). Pero ‘ suelen ser un poco menos detallados para crear y no No tienes que administrar manualmente lo que ‘ estás llevando. (Los escuchas incondicionales hacen una pregunta similar, pero probablemente llegan a esa otra conclusión: que el soporte de OO a nivel de idioma es innecesario cuando hay cierres).
Respuesta
(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 sencilla de pensar en un cierre es una función que se puede almacenar como una variable (denominada «primera -class function «), que tiene una capacidad especial para acceder a otras variables locales del á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 a 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. Porque black
es local al ámbito donde se definió la función , el puntero a esta variable se conserva.
Si pones esto en una página HTML:
- Haga clic para cambiar a negro
- Presione [enter] para ver «verdadero»
- Haga clic de nuevo, vuelve a cambiar a blanco
- Presiona [enter] para ver «falso»
Esto demuestra que ambos tienen acceso al mismo black
, y se puede usar para almacenar el estado sin ningún objeto contenedor.
La llamada a setKeyPress
es para demostrar cómo se puede pasar una función como cualquier variable. El scope conservado en el cierre sigue siendo el que se definió la función.
Los cierres son comúnmente se utilizan como controladores de eventos, especialmente en JavaScript y ActionScript. El buen uso de los cierres lo ayudará a vincular implícitamente variables a controladores de eventos sin tener que crear un contenedor de objetos. Sin embargo, el uso descuidado dará lugar a pérdidas de memoria (como cuando un controlador de eventos no utilizado pero conservado 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.
Comentarios
- Mientras leía su respuesta, Sentí que se encendía una bombilla en mi mente. ¡Muy apreciado! 🙂
- Dado que
black
se declara dentro de una función, no se ‘ t que se destruyan a medida que se desenrolla la pila. ..? - @gablin, eso es lo que hace único a los lenguajes que tienen cierres. Todos los lenguajes con recolección de basura funcionan de la misma manera: cuando no se mantienen más referencias a un objeto, se puede destruir. Siempre que se crea una función en JS, el ámbito local está vinculado a esa función hasta que esa función se destruye.
- @gablin, esa ‘ es una buena pregunta. No ‘ creo que puedan ‘ t & mdash; pero solo mencioné la recolección de basura ya que lo que JS usa y eso ‘ es a lo que parecía estar refiriéndose cuando dijo » Desde
black
se declara dentro de una función, no ‘ t que se destruya «. 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. - Objective-C (y C bajo clang) admite bloques, que son esencialmente cierres, sin recolección de basura. Requiere soporte de tiempo de ejecución y alguna intervención manual en torno a la gestión de la memoria.
Respuesta
Un cierre es básicamente una forma diferente de ver un objeto. Un objeto son datos que tienen una o más funciones vinculadas a él. Un cierre es una función que tiene una o más variables vinculadas. Los dos son básicamente idénticos, al menos a nivel de implementación. La verdadera diferencia está en su procedencia.
En la programación orientada a objetos, declaras una clase de objeto definiendo sus variables miembro y sus métodos (funciones miembro) por adelantado, y luego creas 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 datos.
En un cierre, por otro lado, el objeto no es definido por adelantado como una clase de objeto, o instanciado a través de una llamada al constructor en su código. En cambio, escribe el cierre como una función dentro de otra función. El cierre puede hacer referencia a cualquiera de las variables locales de la función externa, y el compilador lo detecta y mueve estas variables del espacio de 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 bajo el capó, lo pasa como una referencia de función, porque el foco está en su naturaleza como función.
Comentarios
- +1: buena respuesta. Puede ver un cierre como un objeto con un solo método y un objeto arbitrario como una colección de cierres sobre algunos datos subyacentes comunes (las variables miembro del objeto ‘). Creo que estas dos vistas son bastante simétricas.
- Muy buena respuesta. De hecho, explica la percepción del cierre.
- @Mason Wheeler: ¿Dónde se almacenan los datos del cierre? ¿En pila como una función? ¿O en el montón como un objeto?
- @RoboAlex: En el montón, porque ‘ es un objeto que parece una función .
- @RoboAlex: El lugar donde se almacena un cierre y sus datos capturados depende de la implementación. En C ++ se puede almacenar en el montón o en la pila.
Respuesta
El término el cierre proviene del hecho de que un fragmento de código (bloque, función) puede tener variables libres que son cerrado (es decir, vinculado 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
indica dos valores enteros. El nombre v
está vinculado 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 gratuito con la función addConstant
porque la función no contiene ninguna pista sobre qué valor k
está vinculado a (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
está definido. Por ejemplo:
def increaseAll(values: List[Int]): List[Int] = { val k = 2 def addConstant(v: Int): Int = v + k values.map(addConstant) }
Ahora que hemos definido addConstant
en un contexto donde k
está definido, addConstant
se ha convertido en un cierre porque todos sus variables libres ahora están cerradas (ligadas a un valor): addConstant
puede ser invocado y transmitido como si fuera una función. Tenga en cuenta que la variable libre k
está vinculada a un valor cuando el cierre está definido , mientras que la variable de argumento v
está vinculada cuando el cierre es invocado .
Por tanto, un cierre es básicamente una función o bloque de código que puede acceder a valores no locales a través de sus variables libres después de que estas hayan sido limitadas por el contexto.
En muchos idiomas, si use un cierre solo una vez que pueda hacerlo anónimo , por ejemplo,
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). De manera análoga, 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.
Comentarios
- Esto encaja bien con fórmulas cerradas y abiertas en lógica. Gracias por su respuesta.
- @RainDoctor: Las variables libres se definen en fórmulas lógicas y en expresiones de cálculo lambda de manera similar: la lambda en una expresión lambda funciona como un cuantificador en fórmulas lógicas con variables libres / ligadas .
Respuesta
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 espacio de nombres de la función alertValue
devuelto se conectará al espacio de nombres en el que reside la variable closure
. Cuando eliminas toda la función, el El valor de la variable closure
se eliminará, pero hasta entonces, la función alertValue
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 variable closure
y reescribe la función a:
var closure_example = function(){ alertValue : function(){ closure++; alert(closure); } }
Y porque 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 al closure_example
función, escribirá el valor incrementado de la variable closure
porque alert(closure)
es enlazado.
closure_example.alertValue()//alerts value 1 closure_example.alertValue()//alerts value 2 closure_example.alertValue()//alerts value 3 //etc.
Comentarios
- gracias, no lo hice ‘ t prueba el código =) todo parece estar bien ahora.
Respuesta
Un «cierre» es , en esencia, un estado local y un código, combinados en un paquete. Normalmente, 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 ser desconocido.
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 de 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 «estés, el código» dame el libro «da como resultado que sucedan diferentes cosas».
Comentarios
- Olvidó esto: en.wikipedia.org/wiki/Closure_(computer_programming) en su respuesta.
- No, decidí conscientemente no cerrar esa página.
- » Estado y función. «: ¿Puede una función C con una
static
variable local considerarse un cierre? en Haskell involucran el estado? - @Giorgio Closures en Haskell (creo) cierran sobre los argumentos en el ámbito léxico en el que ‘ están definidos, entonces, yo ‘ diría » sí » (aunque, en el mejor de los casos, no estoy familiarizado con Haskell). La función de CA con una variable estática es, en el mejor de los casos, un cierre muy limitado (realmente desea poder crear varios cierres desde una única función, con una
static
variable local, tiene exactamente uno). - Hice esta pregunta a propósito porque creo que una función C con una variable estática no es un cierre: la variable estática se define localmente y solo se conoce dentro del cierre, no tiene acceso el entorno. Además, no estoy 100% seguro, pero formularía su declaración al revés: usa el mecanismo de cierre para crear diferentes funciones (una función es una definición de cierre + un enlace para sus variables libres).
Respuesta
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 a function foo()
pero también hace referencia al estado en el que se dejó foo
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 (así como «normalmente» definimos funciones en un entorno global).
Funcionalmente, puede resolver muchos de los mismos problemas que C ++ (C?) «s» palabra clave «estática», que retiene el estado de una variable local a través de múltiples llamadas a funciones; 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 el estado de toda 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 de 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 parece que en Lua cada vez que se ejecuta una función, su estado se restablece. Digo esto porque los resultados del código anterior serían diferentes si estuviéramos accediendo al función / estado directamente (en lugar de a través de la función anónima que devuelve), ya que pvalue
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 algo más, tal vez alguien puede explicar mejor la naturaleza de la implementación.
PD: No conozco ni un ápice de C ++ 11 (aparte de lo que hay en versiones anteriores), así que tenga en cuenta que esta no es una comparación entre cierres en C ++ 11 y Lua. Además, todas las «líneas dibujadas» de Lua a C ++ son similitudes como 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.
Respuesta
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 miramos 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"); }
Respuesta
Consideremos una función simple:
function f1(x) { // ... something }
Esta función se denomina función de nivel superior porque «no está anidada dentro de ninguna otra función. Cada La función JavaScript asocia consigo misma una lista de objetos denominada » Cadena de alcance «. Esta cadena de alcance es una lista ordenada de objetos. E 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 único 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 esto se invoca la función, JavaScript crea algo llamado «Objeto de activación» y lo coloca en la parte superior de la cadena de alcance. Esto El 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 que los dos objetos se colocan en la cadena de alcance en DIFERENTES momentos. El objeto global se coloca cuando se define la función (es decir, cuando JavaScript analizó la función y creó el objeto de función), y el El objeto de activación entra cuando se invoca la función.
Entonces, ahora sabemos esto:
- Cada función tiene una cadena de alcance asociada
- Cuandola función está definida (cuando se crea el objeto de función), JavaScript guarda una cadena de alcance con esa función
- Para funciones de nivel superior, la cadena de alcance contiene solo el objeto global en el momento de la definición de la función y agrega una objeto de activación en la parte superior en el momento de la invocación
La situación se vuelve 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 sólo el objeto global.
Ahora, cuando se llama a f1
, 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 en vigor. El alcance actual en efecto, la cadena es la de f1
«s. Por lo tanto, la cadena de alcance de f2
» es f1
«s actual cadena de alcance, que contiene el objeto de activación de f1
y el objeto global.
Cuando se llama a f2
, «obtiene» su propio objeto de activación que contiene y
, agregado a su cadena de alcance que ya contiene el objeto de activación de f1
y el objeto global.
Si hubiera otra función anidada definida dentro de 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 y cómo funciona la cadena de alcance, pero todavía no hemos hablado de cierres.
La combinación de un objeto de función y un alcance (un conjunto de vinculaciones de variables) en la que se resuelven las variables de la función se denomina cierre en la literatura 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 en efecto 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 en vigor cuando se definieron. Esto sucede más comúnmente cuando un objeto de función anidado devuelve desde la función dentro de la cual fue definido.
Cuando la función regresa, ese objeto de activación se elimina de la cadena de alcance. Si no hubiera funciones anidadas, no hay más referencias al objeto de activación y se recolecta la basura. Si se definieron funciones anidadas, entonces 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 como 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 se refiere tampoco se recolectará basura.
En nuestro ejemplo anterior, no devolvemos f2
de f1
, por lo tanto, cuando regrese una llamada a f1
, 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í, el f2
que regresa tendrá una cadena de alcance que contiene el objeto de activación de f1
, y por lo tanto no será recolectado como basura. En este punto, si llamamos a f2
, podrá acceder a la f1
«s 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 son «con alcance léxico» , lo que significa que guardan el alcance que estaba activo cuando se definieron en lugar del alcance que estaba activo cuando se les llamó.
Hay una serie de técnicas de programación poderosas que involucran cierres como la 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 lenguajes que admiten cierres. Por ejemplo PHP (5.3+), Python, Ruby, etc.
Respuesta
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 hombre pobre .
Vea la respuesta de Eric Lippert : (extracto a continuación)
El compilador generará un 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; }
¿Tiene sentido?
Además, solicitó comparaciones. VB y JScript crean cierres prácticamente de la misma manera.
Comentarios
- Esta respuesta es una CW porque no ‘ no merezco puntos para Eric ‘ s gran respuesta.Por favor, vote a favor como mejor le parezca. HTH
- -1: Su explicación es demasiado raíz en C #. El cierre se usa en muchos idiomas y es mucho más que azúcar sintáctico en estos idiomas y abarca tanto la función como el estado.
- No, un cierre no es solo un » optimización del compilador » ni azúcar sintáctico. -1