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í?
language-features
closures
gablin
fuente
fuente
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):
Las funciones 1 asignadas
document.onclick
ydisplayValOfBlack
son cierres. Puede ver que ambos hacen referencia a la variable booleanablack
, pero esa variable se asigna fuera de la función. Comoblack
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.
fuente
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.
fuente
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:
En el cuerpo de la función hay dos nombres (variables)
v
yk
que indican dos valores enteros. El nombrev
está enlazado porque se declara como un argumento de la funciónaddConstant
(al observar la declaración de la función sabemos quev
se le asignará un valor cuando se invoque la función). El nombrek
es libre de la funciónaddConstant
porque la función no contiene ninguna pista sobre qué valork
está vinculado (y cómo).Para evaluar una llamada como:
tenemos que asignar
k
un valor, que solo puede suceder si el nombrek
se define en el contexto en el queaddConstant
se define. Por ejemplo:Ahora que lo hemos definido
addConstant
en un contexto dondek
está definido, seaddConstant
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 librek
está vinculada a un valor cuando se define el cierre , mientras que la variable de argumentov
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.
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.
fuente
Una explicación simple en JavaScript:
alert(closure)
utilizará el valor creado previamente declosure
. ElalertValue
espacio de nombres de la función devuelta se conectará al espacio de nombres en el queclosure
reside la variable. Cuando elimine la función completa, el valor de laclosure
variable se eliminará, pero hasta entonces, laalertValue
función siempre podrá leer / escribir el valor de la variableclosure
.Si ejecuta este código, la primera iteración asignará un valor 0 a la
closure
variable y reescribirá la función para:Y debido a que
alertValue
necesita la variable localclosure
para ejecutar la función, se vincula con el valor de la variable local previamente asignadaclosure
.Y ahora cada vez que llame a la
closure_example
función, escribirá el valor incrementado de laclosure
variable porquealert(closure)
está enlazado.fuente
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 ".
fuente
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:
La variable
x
no solo hace referenciafunction foo()
sino que también hace referencia al estado quefoo
se dejó la última vez que regresó. La verdadera magia ocurre cuandofoo
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.
resultados:
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), yapvalue
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 quepvalue
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.
fuente
Un cierre es una función que tiene un estado asociado:
En perl creas cierres como este:
Si nos fijamos en la nueva funcionalidad proporcionada con C ++.
También le permite vincular el estado actual al objeto:
fuente
Consideremos una función simple:
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:
Cuando
f1
se define, obtenemos una cadena de alcance que contiene solo el objeto global.Ahora cuando
f1
se llama, la cadena de alcance def1
obtiene el objeto de activación. Este objeto de activación contiene la variablex
y la variablef2
que es una función. Y, tenga en cuenta quef2
se está definiendo. Por lo tanto, en este punto, JavaScript también guarda una nueva cadena de alcance paraf2
. 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 def1
's. Por lo tantof2
, la cadena de alcance esf1
la cadena de alcance actual , que contiene el objeto de activaciónf1
y el objeto global.Cuando
f2
se llama, obtiene su propio objeto de activación que contieney
, agregado a su cadena de alcance que ya contiene el objeto de activaciónf1
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 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
def1
, por lo tanto, cuando una llamada af1
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:Aquí, la devolución
f2
tendrá una cadena de alcance que contendrá el objeto de activaciónf1
y, por lo tanto, no se recolectará basura. En este punto, si llamamosf2
, podrá acceder af1
la variablex
aunque estemos fuera def1
.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.
fuente
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:
¿Tener sentido?
Además, solicitó comparaciones. VB y JScript crean cierres de la misma manera.
fuente