¿Qué es un 'cierre'?

432

Hice una pregunta sobre Curry y se mencionaron cierres. ¿Qué es un cierre? ¿Cómo se relaciona con el curry?

Ben
fuente
22
Ahora, ¿qué es exactamente el cierre? Algunas respuestas dicen que el cierre es la función. Algunos dicen que es la pila. Algunas respuestas dicen que es el valor "oculto". A mi entender, es la función + variables incluidas.
Roland el
3
Explica qué es un cierre: stackoverflow.com/questions/4103750/…
dietbuddha
También eche un vistazo a ¿Qué es un cierre? en softwareengineering.stackexchange
B12Toaster
Explica qué es un cierre y el caso de uso común: trungk18.com/experience/javascript-closure
Sasuke91

Respuestas:

744

Alcance variable

Cuando declara una variable local, esa variable tiene un alcance. En general, las variables locales solo existen dentro del bloque o función en la que las declara.

function() {
  var a = 1;
  console.log(a); // works
}    
console.log(a); // fails

Si intento acceder a una variable local, la mayoría de los idiomas la buscarán en el ámbito actual y luego subirán a través de los ámbitos principales hasta que alcancen el ámbito raíz.

var a = 1;
function() {
  console.log(a); // works
}    
console.log(a); // works

Cuando se realiza un bloque o función, sus variables locales ya no son necesarias y, por lo general, se quedan sin memoria.

Así es como normalmente esperamos que las cosas funcionen.

Un cierre es un alcance variable local persistente

Un cierre es un alcance persistente que conserva las variables locales incluso después de que la ejecución del código se haya salido de ese bloque. Los idiomas que admiten el cierre (como JavaScript, Swift y Ruby) le permitirán mantener una referencia a un ámbito (incluidos sus ámbitos principales), incluso después de que el bloque en el que se declararon esas variables haya terminado de ejecutarse, siempre que mantenga una referencia a ese bloque o función en alguna parte.

El objeto de alcance y todas sus variables locales están vinculadas a la función y persistirán mientras esa función persista.

Esto nos da portabilidad de funciones. Podemos esperar que cualquier variable que estuviera dentro del alcance cuando la función se definió por primera vez aún esté dentro del alcance cuando luego la llamemos, incluso si llamamos a la función en un contexto completamente diferente.

Por ejemplo

Aquí hay un ejemplo realmente simple en JavaScript que ilustra el punto:

outer = function() {
  var a = 1;
  var inner = function() {
    console.log(a);
  }
  return inner; // this returns a function
}

var fnc = outer(); // execute outer to get inner 
fnc();

Aquí he definido una función dentro de una función. La función interna obtiene acceso a todas las variables locales de la función externa, incluida a. La variable aestá dentro del alcance de la función interna.

Normalmente, cuando sale una función, todas sus variables locales quedan impresionadas. Sin embargo, si devolvemos la función interna y la asignamos a una variable fncpara que persista después de que outerhaya salido, todas las variables que estaban en el alcance cuando innerse definió también persisten . La variable aha sido cerrada, está dentro de un cierre.

Tenga en cuenta que la variable aes totalmente privada para fnc. Esta es una forma de crear variables privadas en un lenguaje de programación funcional como JavaScript.

Como puede adivinar, cuando lo llamo fnc()imprime el valor de a, que es "1".

En un lenguaje sin cierre, la variable ahabría sido recogida de basura y descartada cuando la función outersalió. Llamar a fnc habría arrojado un error porque aya no existe.

En JavaScript, la variable apersiste porque el alcance de la variable se crea cuando la función se declara por primera vez y persiste mientras la función continúe existiendo.

apertenece al ámbito de outer. El alcance de innertiene un puntero padre al alcance de outer. fnces una variable que apunta a inner. apersiste mientras fncpersista. aEstá dentro del cierre.

superluminary
fuente
116
Pensé que este era un ejemplo bastante bueno y fácil de entender.
user12345613
16
Gracias por la increíble explicación, he visto muchos, pero este es el momento en que realmente lo tengo.
Dimitar Dimitrov
2
¿Podría tener un ejemplo de cómo funciona esto en una biblioteca como JQuery como se indica en el segundo párrafo? No entendí totalmente eso.
DPM
66
Hola Jubbat, sí, abre jquery.js y mira la primera línea. Verá que se abre una función. Ahora salte al final, verá window.jQuery = window. $ = JQuery. Entonces la función se cierra y se ejecuta automáticamente. Ahora tiene acceso a la función $, que a su vez tiene acceso a las otras funciones definidas en el cierre. Eso responde tu pregunta?
superluminario
44
La mejor explicación en la web. Mucho más simple de lo que pensaba
Mantis
95

Daré un ejemplo (en JavaScript):

function makeCounter () {
  var count = 0;
  return function () {
    count += 1;
    return count;
  }
}

var x = makeCounter();

x(); returns 1

x(); returns 2

...etc...

Lo que hace esta función, makeCounter, es devolver una función, que hemos llamado x, que contará por uno cada vez que se llame. Como no estamos proporcionando ningún parámetro para x, de alguna manera debe recordar el recuento. Sabe dónde encontrarlo en función de lo que se denomina alcance léxico: debe buscar el lugar donde está definido para encontrar el valor. Este valor "oculto" es lo que se llama un cierre.

Aquí está mi ejemplo de curry nuevamente:

function add (a) {
  return function (b) {
    return a + b;
  }
}

var add3 = add(3);

add3(4); returns 7

Lo que puede ver es que cuando llama a add con el parámetro a (que es 3), ese valor está contenido en el cierre de la función devuelta que estamos definiendo como add3. De esa forma, cuando llamamos a add3, sabe dónde encontrar el valor para realizar la suma.

Kyle Cronin
fuente
44
IDK, qué idioma (probablemente F #) ha utilizado en el idioma anterior. ¿Podría dar el ejemplo anterior en pseudocódigo? Estoy teniendo dificultades para entender esto.
usuario
1
@crucifiedsoul Es el esquema. ftp.cs.indiana.edu/pub/scheme-repository/doc/pubs/intro.txt
Kyle Cronin
3
@KyleCronin Gran ejemplo, gracias. P: ¿Es más correcto decir "el valor oculto se llama cierre" o es "la función que oculta el valor es el cierre"? ¿O "el proceso de ocultar el valor es el cierre"? ¡Gracias!
2
@RobertHume Buena pregunta. Semánticamente, el término "cierre" es algo ambiguo. Mi definición personal es que la combinación del valor oculto y el uso de la función de cierre constituye el cierre.
Kyle Cronin
1
@KyleCronin Gracias, tengo un esquema a mitad de período el lunes. :) Quería tener el concepto de "cierre" sólido en mi cabeza. ¡Gracias por publicar esta gran respuesta a la pregunta de OP!
58

La respuesta de Kyle es bastante buena. Creo que la única aclaración adicional es que el cierre es básicamente una instantánea de la pila en el momento en que se crea la función lambda. Luego, cuando la función se vuelve a ejecutar, la pila se restaura a ese estado antes de ejecutar la función. Por lo tanto, como Kyle menciona, ese valor oculto ( count) está disponible cuando se ejecuta la función lambda.

Ben Childs
fuente
14
No se trata solo de la pila, sino de los ámbitos léxicos que se conservan, independientemente de si están almacenados en la pila o en el montón (o en ambos).
Matt Fenwick, el
38

En primer lugar, al contrario de lo que la mayoría de las personas aquí te dicen, ¡el cierre no es una función ! Entonces que es ?
Es un conjunto de símbolos definidos en el "contexto circundante" de una función (conocido como su entorno ) que la convierten en una expresión CERRADA (es decir, una expresión en la que cada símbolo está definido y tiene un valor, por lo que puede evaluarse).

Por ejemplo, cuando tiene una función de JavaScript:

function closed(x) {
  return x + 3;
}

es una expresión cerrada porque todos los símbolos que aparecen en él están definidos en él (sus significados son claros), por lo que puede evaluarlo. En otras palabras, es autónomo .

Pero si tienes una función como esta:

function open(x) {
  return x*y + 3;
}

es una expresión abierta porque contiene símbolos que no se han definido en ella. A saber, y. Al observar esta función, no podemos decir qué yes y qué significa, no sabemos su valor, por lo que no podemos evaluar esta expresión. Es decir, no podemos llamar a esta función hasta que digamos qué yse supone que significa en ella. Esto yse llama una variable libre .

Esto ypide una definición, pero esta definición no es parte de la función: se define en otro lugar, en su "contexto circundante" (también conocido como el entorno ). Al menos eso es lo que esperamos: P

Por ejemplo, podría definirse globalmente:

var y = 7;

function open(x) {
  return x*y + 3;
}

O podría definirse en una función que lo envuelva:

var global = 2;

function wrapper(y) {
  var w = "unused";

  return function(x) {
    return x*y + 3;
  }
}

La parte del entorno que da a las variables libres en una expresión sus significados, es el cierre . Se llama de esta manera, porque convierte una expresión abierta en una cerrada , al proporcionar estas definiciones faltantes para todas sus variables libres , de modo que podamos evaluarla.

En el ejemplo anterior, la función interna (que no le dimos un nombre porque no la necesitamos) es una expresión abierta porque la variable yen ella es libre : su definición está fuera de la función, en la función que la envuelve . El entorno para esa función anónima es el conjunto de variables:

{
  global: 2,
  w: "unused",
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

Ahora, el cierre es esa parte de este entorno que cierra la función interna al proporcionar las definiciones para todas sus variables libres . En nuestro caso, la única variable libre en la función interna era y, por lo que el cierre de esa función es este subconjunto de su entorno:

{
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

Los otros dos símbolos definidos en el entorno no forman parte del cierre de esa función, ya que no requiere que se ejecuten. No son necesarios para cerrarlo .

Más sobre la teoría detrás de eso aquí: https://stackoverflow.com/a/36878651/434562

Vale la pena señalar que en el ejemplo anterior, la función de contenedor devuelve su función interna como un valor. El momento en que llamamos a esta función puede ser remoto en el tiempo desde el momento en que la función se ha definido (o creado). En particular, su función de ajuste ya no se está ejecutando, y sus parámetros que han estado en la pila de llamadas ya no están allí: P Esto causa un problema, porque la función interna necesita yestar allí cuando se llama. En otras palabras, requiere que las variables de su cierre sobrevivan de alguna manera a la función de envoltura y estén allí cuando sea necesario. Por lo tanto, la función interna tiene que hacer una instantánea de estas variables que cierran y las almacenan en un lugar seguro para su uso posterior. (En algún lugar fuera de la pila de llamadas).

Y esta es la razón por la cual las personas a menudo confunden el término cierre con ese tipo especial de función que puede hacer tales instantáneas de las variables externas que usan, o la estructura de datos utilizada para almacenar estas variables para más adelante. Pero espero que entienda ahora que son no el propio cierre - que son sólo maneras de poner en práctica los cierres en un lenguaje de programación o mecanismos del lenguaje que permite a las variables de cierre de la función para estar allí cuando sea necesario. Hay muchos conceptos erróneos sobre los cierres que (innecesariamente) hacen que este tema sea mucho más confuso y complicado de lo que realmente es.

SasQ
fuente
1
Una analogía que podría ayudar a los principiantes a esto es que un cierre ata todos los cabos sueltos , que es lo que hace una persona cuando busca el cierre (o resuelve todas las referencias necesarias, o ...). Bueno, me ayudó a pensar de esa manera: o)
Will Crawford
He leído muchas definiciones de cierre a lo largo de los años, pero creo que esta es mi favorita hasta ahora. Supongo que todos tenemos nuestra propia forma de mapear mentalmente conceptos como este y este muy en desacuerdo con el mío.
Jason S.
29

Un cierre es una función que puede hacer referencia al estado en otra función. Por ejemplo, en Python, esto usa el cierre "interno":

def outer (a):
    b = "variable in outer()"
    def inner (c):
        print a, b, c
    return inner

# Now the return value from outer() can be saved for later
func = outer ("test")
func (1) # prints "test variable in outer() 1
John Millikin
fuente
23

Para facilitar la comprensión de los cierres, puede ser útil examinar cómo se pueden implementar en un lenguaje de procedimiento. Esta explicación seguirá una implementación simplista de cierres en Scheme.

Para comenzar, debo presentar el concepto de un espacio de nombres. Cuando ingresa un comando en un intérprete de esquemas, debe evaluar los diversos símbolos en la expresión y obtener su valor. Ejemplo:

(define x 3)

(define y 4)

(+ x y) returns 7

Las expresiones de definición almacenan el valor 3 en el lugar para x y el valor 4 en el lugar para y. Luego, cuando llamamos (+ xy), el intérprete busca los valores en el espacio de nombres y puede realizar la operación y devolver 7.

Sin embargo, en Scheme hay expresiones que le permiten anular temporalmente el valor de un símbolo. Aquí hay un ejemplo:

(define x 3)

(define y 4)

(let ((x 5))
   (+ x y)) returns 9

x returns 3

Lo que hace la palabra clave let es introducir un nuevo espacio de nombres con x como valor 5. Notarás que todavía puede ver que y es 4, haciendo que la suma devuelta sea 9. También puedes ver que una vez que la expresión ha terminado x vuelve a ser 3. En este sentido, x ha sido temporalmente enmascarado por el valor local.

Los lenguajes procesales y orientados a objetos tienen un concepto similar. Cada vez que declaras una variable en una función que tiene el mismo nombre que una variable global, obtienes el mismo efecto.

¿Cómo implementaríamos esto? Una manera simple es con una lista vinculada: la cabecera contiene el nuevo valor y la cola contiene el antiguo espacio de nombres. Cuando necesita buscar un símbolo, comienza en la cabeza y baja por la cola.

Ahora pasemos a la implementación de funciones de primera clase por el momento. Más o menos, una función es un conjunto de instrucciones para ejecutar cuando se llama a la función que culmina en el valor de retorno. Cuando leemos una función, podemos almacenar estas instrucciones detrás de escena y ejecutarlas cuando se llama a la función.

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns ?

Definimos x para que sea 3 y plus-x para que sea su parámetro, y, más el valor de x. Finalmente llamamos a plus-x en un entorno donde x ha sido enmascarado por una nueva x, esta valoró 5. Si simplemente almacenamos la operación, (+ xy), para la función plus-x, ya que estamos en el contexto de x siendo 5 el resultado devuelto sería 9. Esto es lo que se llama alcance dinámico.

Sin embargo, Scheme, Common Lisp y muchos otros lenguajes tienen lo que se denomina alcance léxico: además de almacenar la operación (+ xy) también almacenamos el espacio de nombres en ese punto en particular. De esa manera, cuando buscamos los valores, podemos ver que x, en este contexto, es realmente 3. Esto es un cierre.

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns 7

En resumen, podemos usar una lista vinculada para almacenar el estado del espacio de nombres en el momento de la definición de la función, lo que nos permite acceder a las variables desde los ámbitos adjuntos, así como también nos brinda la capacidad de enmascarar localmente una variable sin afectar el resto del programa.

Kyle Cronin
fuente
Bien, gracias a tu respuesta, creo que finalmente tengo una idea de qué se trata el cierre. Pero hay una gran pregunta: "podemos usar una lista vinculada para almacenar el estado del espacio de nombres en el momento de la definición de la función, lo que nos permite acceder a variables que de otro modo ya no estarían dentro del alcance". Why do we want to access variables that are out of scope? when we say let x = 5, we want x to be 5 and not 3. What is happening?
Lazer
@Laser: Lo siento, esa oración no tenía mucho sentido, así que la actualicé. Espero que tenga más sentido ahora. Además, no piense en la lista vinculada como un detalle de implementación (ya que es muy ineficiente) sino como una forma simple de conceptualizar cómo se podría hacer.
Kyle Cronin
10

Aquí hay un ejemplo del mundo real de por qué Closures patea traseros ... Esto es sacado directamente de mi código Javascript. Déjame ilustrar.

Function.prototype.delay = function(ms /*[, arg...]*/) {
  var fn = this,
      args = Array.prototype.slice.call(arguments, 1);

  return window.setTimeout(function() {
      return fn.apply(fn, args);
  }, ms);
};

Y así es como lo usarías:

var startPlayback = function(track) {
  Player.play(track);  
};
startPlayback(someTrack);

Ahora imagine que desea que la reproducción comience con retraso, como por ejemplo 5 segundos después de que se ejecute este fragmento de código. Bueno, eso es fácil con delaysu cierre:

startPlayback.delay(5000, someTrack);
// Keep going, do other things

Cuando llama delaycon 5000ms, se ejecuta el primer fragmento y almacena los argumentos pasados ​​en su cierre. Luego, 5 segundos después, cuando setTimeoutocurre la devolución de llamada, el cierre aún mantiene esas variables, por lo que puede llamar a la función original con los parámetros originales.
Este es un tipo de curry o decoración de funciones.

Sin cierres, tendría que mantener de alguna manera el estado de esas variables fuera de la función, ensuciando el código fuera de la función con algo que lógicamente pertenece dentro de ella. El uso de cierres puede mejorar en gran medida la calidad y la legibilidad de su código.

adamJLev
fuente
1
Cabe señalar que extender el lenguaje o los objetos host generalmente se consideran algo malo, ya que son parte del espacio de nombres global
Jon Cooke,
9

Las funciones que no contienen variables libres se denominan funciones puras.

Las funciones que contienen una o más variables libres se denominan cierres.

var pure = function pure(x){
  return x 
  // only own environment is used
}

var foo = "bar"

var closure = function closure(){
  return foo 
  // foo is a free variable from the outer environment
}

src: https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure

soundyogi
fuente
¿Por qué es esto menospreciado? En realidad, está mucho más "en el camino correcto" con esa distinción en variables libres y variables ligadas, y funciones puras / cerradas y funciones impuras / abiertas, que la mayoría de las otras respuestas despistadas aquí: P (descuento por cierres confusos con funciones estar cerrado).
SasQ
Tengo ninguna idea, de verdad. Es por eso que StackOverflow apesta. Solo mira la fuente de mi respuesta. ¿Quién puede discutir con eso?
soundyogi
SO no apesta y nunca escuché el término "variable libre"
Kai
Es difícil hablar de cierres sin mencionar variables libres. Solo búscalos. Terminología estándar de CS.
ComDubh
Sin embargo, "las funciones que contienen una o más variables libres se llaman cierres" no es una definición correcta: los cierres son siempre objetos de primera clase.
ComDubh
7

tl; dr

Un cierre es una función y su alcance se asigna a (o se usa como) una variable. Por lo tanto, el cierre del nombre: el alcance y la función están encerrados y utilizados como cualquier otra entidad.

Explicación profunda del estilo de Wikipedia

Según Wikipedia, un cierre es:

Técnicas para implementar el enlace de nombres con ámbito léxico en idiomas con funciones de primera clase.

Qué significa eso? Veamos algunas definiciones.

Explicaré los cierres y otras definiciones relacionadas utilizando este ejemplo:

function startAt(x) {
    return function (y) {
        return x + y;
    }
}

var closure1 = startAt(1);
var closure2 = startAt(5);

console.log(closure1(3)); // 4 (x == 1, y == 3)
console.log(closure2(3)); // 8 (x == 5, y == 3)

Funciones de primera clase

Básicamente, eso significa que podemos usar funciones como cualquier otra entidad . Podemos modificarlos, pasarlos como argumentos, devolverlos de funciones o asignarlos a variables. Técnicamente hablando, son ciudadanos de primera clase. , de ahí el nombre: funciones de primera clase.

En el ejemplo anterior, startAtdevuelve una función ( anónima ) a la que se asigna la función closure1y closure2. Entonces, como ve, JavaScript trata las funciones como cualquier otra entidad (ciudadanos de primera clase).

Enlace de nombre

El enlace de nombres consiste en descubrir a qué datos hace referencia una variable (identificador) . El alcance es realmente importante aquí, ya que eso es lo que determinará cómo se resuelve un enlace.

En el ejemplo anterior:

  • En el ámbito de la función anónima interna, yestá obligado a3 .
  • En startAtel ámbito de aplicación, xestá vinculado 1ao 5(según el cierre).

Dentro del alcance de la función anónima, xno está vinculado a ningún valor, por lo que debe resolverse en un startAtalcance superior ( s).

Alcance léxico

Como dice Wikipedia , el alcance:

Es la región de un programa de computadora donde el enlace es válido: donde el nombre puede usarse para referirse a la entidad .

Hay dos técnicas:

  • Alcance léxico (estático): la definición de una variable se resuelve al buscar su bloque o función que contiene, luego, si eso falla al buscar el bloque que contiene el exterior, y así sucesivamente.
  • Ámbito dinámico: se busca la función de llamada, luego la función que llamó a esa función de llamada, y así sucesivamente, avanzando en la pila de llamadas.

Para obtener más explicaciones, consulte esta pregunta y eche un vistazo a Wikipedia .

En el ejemplo anterior, podemos ver que JavaScript tiene un ámbito léxico, porque cuando xse resuelve, el enlace se busca en el ámbito superior ( startAt's), en función del código fuente (la función anónima que busca x se define dentro startAt) y no se basa en la pila de llamadas, la forma (el alcance donde) se llamó a la función.

Envolver (cerrar)

En nuestro ejemplo, cuando llamamos startAt, devolverá una función (de primera clase) a la que se le asignará closure1y, por lo closure2tanto, se crea un cierre, porque las variables pasadas 1y 5se guardarán dentro startAtdel alcance, que se adjuntará con el devuelto función anónima. Cuando llamamos a esta función anónima vía closure1y closure2con el mismo argumento ( 3), el valor de yse encontrará de inmediato (ya que ese es el parámetro de esa función), pero xno está vinculado en el alcance de la función anónima, por lo que la resolución continúa en el alcance de la función superior (léxicamente) (que se guardó en el cierre) donde xse encuentra vinculado a cualquiera 1o5. Ahora sabemos todo para la suma, por lo que el resultado puede devolverse y luego imprimirse.

Ahora debe comprender los cierres y cómo se comportan, que es una parte fundamental de JavaScript.

Zurra

Ah, y también aprendiste de qué se trata el curry : utilizas funciones (cierres) para pasar cada argumento de una operación en lugar de usar una función con múltiples parámetros.

totymedli
fuente
5

El cierre es una característica en JavaScript donde una función tiene acceso a sus propias variables de alcance, acceso a las variables de la función externa y acceso a las variables globales.

El cierre tiene acceso a su alcance de función externa incluso después de que la función externa ha regresado. Esto significa que un cierre puede recordar y acceder a variables y argumentos de su función externa incluso después de que la función haya finalizado.

La función interna puede acceder a las variables definidas en su propio alcance, el alcance de la función externa y el alcance global. Y la función externa puede acceder a la variable definida en su propio alcance y el alcance global.

Ejemplo de cierre :

var globalValue = 5;

function functOuter() {
  var outerFunctionValue = 10;

  //Inner function has access to the outer function value
  //and the global variables
  function functInner() {
    var innerFunctionValue = 5;
    alert(globalValue + outerFunctionValue + innerFunctionValue);
  }
  functInner();
}
functOuter();  

La salida será 20, que suma la variable propia de su función interna, la variable de función externa y el valor de la variable global.

rahul sharma
fuente
4

En una situación normal, las variables están sujetas a una regla de alcance: las variables locales solo funcionan dentro de la función definida. El cierre es una forma de romper esta regla temporalmente por conveniencia.

def n_times(a_thing)
  return lambda{|n| a_thing * n}
end

en el código anterior, lambda(|n| a_thing * n}es el cierre porque a_thinges referido por lambda (un creador de funciones anónimas).

Ahora, si coloca la función anónima resultante en una variable de función.

foo = n_times(4)

foo romperá la regla de alcance normal y comenzará a usar 4 internamente.

foo.call(3)

devuelve 12.

Eugene Yokota
fuente
2

En resumen, el puntero de función es solo un puntero a una ubicación en la base del código del programa (como el contador del programa). Mientras que cierre = puntero de función + marco de pila .

.

RoboAlex
fuente
1

• Un cierre es un subprograma y el entorno de referencia donde se definió.

- El entorno de referencia es necesario si se puede llamar al subprograma desde cualquier lugar arbitrario del programa

- Un lenguaje de alcance estático que no permite subprogramas anidados no necesita cierres

- Los cierres solo son necesarios si un subprograma puede acceder a variables en ámbitos de anidamiento y se puede llamar desde cualquier lugar

- Para admitir los cierres, una implementación puede necesitar proporcionar una extensión ilimitada a algunas variables (porque un subprograma puede acceder a una variable no local que normalmente ya no está activa)

Ejemplo

function makeAdder(x) {
return function(y) {return x + y;}
}
var add10 = makeAdder(10);
var add5 = makeAdder(5);
document.write(″add 10 to 20: ″ + add10(20) +
″<br />″);
document.write(″add 5 to 20: ″ + add5(20) +
″<br />″);
BoraKurucu
fuente
0

Aquí hay otro ejemplo de la vida real, y el uso de un lenguaje de script popular en los juegos: Lua. Necesitaba cambiar ligeramente la forma en que funcionaba una función de biblioteca para evitar un problema con stdin no disponible.

local old_dofile = dofile

function dofile( filename )
  if filename == nil then
    error( 'Can not use default of stdin.' )
  end

  old_dofile( filename )
end

El valor de old_dofile desaparece cuando este bloque de código termina su alcance (porque es local), sin embargo, el valor se ha encerrado en un cierre, por lo que la nueva función de dofile redefinida PUEDE acceder a él, o más bien una copia almacenada junto con la función como un 'upvalue'.

Nigel Atkinson
fuente
0

Desde Lua.org :

Cuando una función se escribe encerrada en otra función, tiene acceso completo a las variables locales desde la función de cerramiento; Esta característica se denomina alcance léxico. Aunque eso puede sonar obvio, no lo es. El alcance léxico, más las funciones de primera clase, es un concepto poderoso en un lenguaje de programación, pero pocos lenguajes admiten ese concepto.

usuario5731811
fuente
0

Si eres del mundo Java, puedes comparar un cierre con una función miembro de una clase. Mira este ejemplo

var f=function(){
  var a=7;
  var g=function(){
    return a;
  }
  return g;
}

La función ges un cierre: se gcierra a. Por glo tanto, se puede comparar con una función miembro, ase puede comparar con un campo de clase y la función fcon una clase.

revs ericj
fuente
0

Cierres Cuando tenemos una función definida dentro de otra función, la función interna tiene acceso a las variables declaradas en la función externa. Los cierres se explican mejor con ejemplos. En el Listado 2-18, puede ver que la función interna tiene acceso a una variable (variableInOuterFunction) desde el alcance externo. Las variables en la función externa han sido cerradas (o ligadas) por la función interna. De ahí el término cierre. El concepto en sí mismo es bastante simple y bastante intuitivo.

Listing 2-18:
    function outerFunction(arg) {
     var variableInOuterFunction = arg;

     function bar() {
             console.log(variableInOuterFunction); // Access a variable from the outer scope
     }
     // Call the local function to demonstrate that it has access to arg
     bar(); 
    }
    outerFunction('hello closure!'); // logs hello closure!

fuente: http://index-of.es/Varios/Basarat%20Ali%20Syed%20(auth.)-Beginning%20Node.js-Apress%20(2014).pdf

shohan
fuente
0

Eche un vistazo debajo del código para comprender el cierre en más profundidad:

        for(var i=0; i< 5; i++){            
            setTimeout(function(){
                console.log(i);
            }, 1000);                        
        }

Aquí lo que saldrá? 0,1,2,3,4eso no será5,5,5,5,5 por cierre

Entonces, ¿cómo se resolverá? La respuesta está abajo:

       for(var i=0; i< 5; i++){
           (function(j){     //using IIFE           
                setTimeout(function(){
                               console.log(j);
                           },1000);
            })(i);          
        }

Permítanme explicar de manera simple, cuando una función creada no sucede nada hasta que se llama así para el bucle en el primer código llamado 5 veces, pero no se llama inmediatamente, así que cuando se llama, es decir, después de 1 segundo y también esto es asíncrono, entonces antes de que finalice el bucle y almacene el valor 5 en var i y finalmente ejecutar la setTimeoutfunción cinco veces e imprimir5,5,5,5,5

Aquí cómo se resuelve usando IIFE, es decir, la expresión de la función de invocación inmediata

       (function(j){  //i is passed here           
            setTimeout(function(){
                           console.log(j);
                       },1000);
        })(i);  //look here it called immediate that is store i=0 for 1st loop, i=1 for 2nd loop, and so on and print 0,1,2,3,4

Para obtener más información, comprenda el contexto de ejecución para comprender el cierre.

  • Hay una solución más para resolver esto usando let (función ES6) pero bajo el capó la función anterior funciona

     for(let i=0; i< 5; i++){           
         setTimeout(function(){
                        console.log(i);
                    },1000);                        
     }
    
    Output: 0,1,2,3,4
    

=> Más explicación:

En la memoria, cuando para el bucle ejecute la imagen haga lo siguiente:

Bucle 1)

     setTimeout(function(){
                    console.log(i);
                },1000);  

Lazo 2)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Lazo 3)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Lazo 4)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Lazo 5)

     setTimeout(function(){
                    console.log(i);
                },1000);  

Aquí no se ejecuta y luego, después de completar el ciclo, var i almacenó el valor 5 en la memoria, pero su alcance siempre es visible en su función secundaria, por lo que cuando la función se ejecuta al revés setTimeoutcinco veces se imprime5,5,5,5,5

para resolver este uso IIFE como se explica anteriormente.

descuento
fuente
gracias por tu respuesta. sería más legible si separara el código de la explicación. (no sangría líneas que no son código)
eMBee
0

Curry: le permite evaluar parcialmente una función pasando solo un subconjunto de sus argumentos. Considera esto:

function multiply (x, y) {
  return x * y;
}

const double = multiply.bind(null, 2);

const eight = double(4);

eight == 8;

Cierre: Un cierre no es más que acceder a una variable fuera del alcance de una función. Es importante recordar que una función dentro de una función o una función anidada no es un cierre. Los cierres siempre se usan cuando es necesario acceder a las variables fuera del alcance de la función.

function apple(x){
   function google(y,z) {
    console.log(x*y);
   }
   google(7,2);
}

apple(3);

// the answer here will be 21
usuario11335201
fuente
0

El cierre es muy fácil. Podemos considerarlo de la siguiente manera: Cierre = función + su entorno léxico

Considere la siguiente función:

function init() {
    var name = “Mozilla”;
}

¿Cuál será el cierre en el caso anterior? Función init () y variables en su entorno léxico, es decir, nombre. Cierre = init () + nombre

Considere otra función:

function init() {
    var name = “Mozilla”;
    function displayName(){
        alert(name);
}
displayName();
}

¿Cuáles serán los cierres aquí? La función interna puede acceder a las variables de la función externa. displayName () puede acceder al nombre de la variable declarada en la función padre, init (). Sin embargo, se utilizarán las mismas variables locales en displayName () si existen.

Cierre 1: función init + (variable de nombre + displayName () función) -> alcance léxico

Cierre 2: función función + (variable de nombre) -> alcance léxico

Rumel
fuente
0

Los cierres proporcionan JavaScript con estado.

El estado en la programación simplemente significa recordar cosas.

Ejemplo

var a = 0;

a = a + 1; // => 1
a = a + 1; // => 2
a = a + 1; // => 3

En el caso anterior, el estado se almacena en la variable "a". Seguimos agregando 1 a "a" varias veces. Solo podemos hacer eso porque podemos "recordar" el valor. El titular del estado, "a", mantiene ese valor en la memoria.

A menudo, en los lenguajes de programación, desea realizar un seguimiento de las cosas, recordar información y acceder a ella en otro momento.

Esto, en otros idiomas , se logra comúnmente mediante el uso de clases. Una clase, al igual que las variables, realiza un seguimiento de su estado. Y las instancias de esa clase, a su vez, también tienen estado dentro de ellas. Estado simplemente significa información que puede almacenar y recuperar más tarde.

Ejemplo

class Bread {
  constructor (weight) {
    this.weight = weight;
  }

  render () {
    return `My weight is ${this.weight}!`;
  }
}

¿Cómo podemos acceder al "peso" desde el método "render"? Bueno, gracias al estado. Cada instancia de la clase Pan puede representar su propio peso al leerla desde el "estado", un lugar en la memoria donde podríamos almacenar esa información.

Ahora, JavaScript es un lenguaje muy único que históricamente no tiene clases (ahora sí, pero bajo el capó solo hay funciones y variables), por lo que Closures proporciona una forma para que JavaScript recuerde cosas y acceda a ellas más tarde.

Ejemplo

var n = 0;
var count = function () {
  n = n + 1;
  return n;
};

count(); // # 1
count(); // # 2
count(); // # 3

El ejemplo anterior logró el objetivo de "mantener el estado" con una variable. ¡Esto es genial! Sin embargo, esto tiene la desventaja de que la variable (el "titular" del estado) ahora está expuesta. Podemos hacerlo mejor. Podemos usar cierres.

Ejemplo

var countGenerator = function () {
  var n = 0;
  var count = function () {
    n = n + 1;
    return n;
  };

  return count;
};

var count = countGenerator();
count(); // # 1
count(); // # 2
count(); // # 3

Esto es fantástico.

Ahora nuestra función "contar" puede contar. Solo puede hacerlo porque puede "mantener" el estado. El estado en este caso es la variable "n". Esta variable ahora está cerrada. Cerrado en tiempo y espacio. A tiempo porque nunca podrás recuperarlo, cambiarlo, asignarle un valor o interactuar directamente con él. En el espacio porque está anidado geográficamente dentro de la función "countGenerator".

¿Por qué es esto fantástico? Porque sin involucrar ninguna otra herramienta sofisticada y complicada (por ejemplo, clases, métodos, instancias, etc.) podemos 1. ocultar 2. control desde la distancia

Ocultamos el estado, la variable "n", lo que la convierte en una variable privada. También hemos creado una API que puede controlar esta variable de forma predefinida. En particular, podemos llamar a la API así "count ()" y eso agrega 1 a "n" desde una "distancia". De ninguna manera, nadie podrá acceder a "n" excepto a través de la API.

JavaScript es realmente sorprendente en su simplicidad.

Los cierres son una gran parte de por qué esto es así.

Claudio
fuente
0

Un ejemplo simple en Groovy para su referencia:

def outer() {
    def x = 1
    return { -> println(x)} // inner
}
def innerObj = outer()
innerObj() // prints 1
GraceMeng
fuente