¿Cómo explicaría los cierres de JavaScript a alguien que conozca los conceptos en los que consisten (por ejemplo, funciones, variables y similares), pero que no comprende los cierres por sí mismos?
He visto el ejemplo de Scheme dado en Wikipedia, pero desafortunadamente no ayudó.
closure
código difícil de leer en comparación con elObject Literal
que se reutiliza y reduce la sobrecarga de todos modos, pero requiere un código de envoltura 100% menor.Respuestas:
Un cierre es una combinación de:
Un entorno léxico es parte de cada contexto de ejecución (marco de pila) y es un mapa entre identificadores (es decir, nombres de variables locales) y valores.
Cada función en JavaScript mantiene una referencia a su entorno léxico externo. Esta referencia se utiliza para configurar el contexto de ejecución creado cuando se invoca una función. Esta referencia permite que el código dentro de la función "vea" las variables declaradas fuera de la función, independientemente de cuándo y dónde se llama la función.
Si una función fue llamada por una función, que a su vez fue llamada por otra función, entonces se crea una cadena de referencias a entornos léxicos externos. Esta cadena se llama cadena de alcance.
En el siguiente código, se
inner
forma un cierre con el entorno léxico del contexto de ejecución creado cuandofoo
se invoca, cerrando sobre variablesecret
:En otras palabras: en JavaScript, las funciones llevan una referencia a una "caja de estado" privada, a la que solo ellas (y cualquier otra función declarada dentro del mismo entorno léxico) tienen acceso. Este cuadro de estado es invisible para la persona que llama de la función, y ofrece un excelente mecanismo para ocultar datos y encapsular.
Y recuerde: las funciones en JavaScript se pueden pasar como variables (funciones de primera clase), lo que significa que estos pares de funcionalidad y estado se pueden pasar alrededor de su programa: similar a cómo podría pasar una instancia de una clase en C ++.
Si JavaScript no tuviera cierres, entonces se tendría que pasar más estado entre funciones explícitamente , haciendo que las listas de parámetros sean más largas y el código sea más ruidoso.
Por lo tanto, si desea que una función siempre tenga acceso a un estado privado, puede usar un cierre.
... y con frecuencia nos qué queremos estado asociado con una función. Por ejemplo, en Java o C ++, cuando agrega una variable de instancia privada y un método a una clase, está asociando el estado con la funcionalidad.
En C y en la mayoría de los otros lenguajes comunes, después de que una función regresa, todas las variables locales ya no son accesibles porque el marco de la pila se destruye. En JavaScript, si declara una función dentro de otra función, las variables locales de la función externa pueden permanecer accesibles después de regresar de ella. De esta manera, en el código anterior,
secret
permanece disponible para el objeto de funcióninner
, después de que ha sido devueltofoo
.Usos de cierres
Los cierres son útiles siempre que necesite un estado privado asociado con una función. Este es un escenario muy común, y recuerde: JavaScript no tenía una sintaxis de clase hasta 2015, y todavía no tiene una sintaxis de campo privado. Los cierres satisfacen esta necesidad.
Variables de instancia privada
En el siguiente código, la función se
toString
cierra sobre los detalles del automóvil.Programacion Funcional
En el siguiente código, la función se
inner
cierra sobre ambosfn
yargs
.Programación Orientada a Eventos
En el siguiente código, la función se
onClick
cierra sobre la variableBACKGROUND_COLOR
.Modularización
En el siguiente ejemplo, todos los detalles de implementación están ocultos dentro de una expresión de función ejecutada inmediatamente. Las funciones
tick
y eltoString
cierre sobre el estado privado y las funciones que necesitan para completar su trabajo. Los cierres nos han permitido modularizar y encapsular nuestro código.Ejemplos
Ejemplo 1
Este ejemplo muestra que las variables locales no se copian en el cierre: el cierre mantiene una referencia a las variables originales propios . Es como si el marco de la pila permaneciera vivo en la memoria incluso después de que la función externa salga.
Ejemplo 2
En el siguiente código, tres métodos
log
,increment
yupdate
todos cerca en el mismo entorno léxico.Y cada vez que
createObject
se llama, se crea un nuevo contexto de ejecución (marco de pila) y se crea una variable completamente nuevax
y un nuevo conjunto de funciones (log
etc.) que se cierran sobre esta nueva variable.Ejemplo 3
Si está utilizando variables declaradas usando
var
, tenga cuidado de comprender qué variable está cerrando. Las variables declaradas usandovar
son izadas. Esto es mucho menos problemático en JavaScript moderno debido a la introducción delet
yconst
.En el siguiente código, cada vez alrededor del ciclo,
inner
se crea una nueva función , que se cierrai
. Pero debido a quevar i
se iza fuera del bucle, todas estas funciones internas se cierran sobre la misma variable, lo que significa que el valor final dei
(3) se imprime tres veces.Puntos finales
function
desde dentro de otra función es el ejemplo clásico de un cierre, porque el estado dentro de la función externa está implícitamente disponible para la función interna devuelta, incluso después de que la función externa haya completado la ejecución.eval()
dentro de una función, se usa un cierre. El textoeval
puede hacer referencia a variables locales de la función, y en modo no estricto incluso puede crear nuevas variables locales mediante el usoeval('var foo = …')
.new Function(…)
(el constructor de funciones ) dentro de una función, no se cierra sobre su entorno léxico: en su lugar, se cierra sobre el contexto global. La nueva función no puede hacer referencia a las variables locales de la función externa.Enlaces
fuente
Closures are functions that refer to independent (free) variables. In other words, the function defined in the closure 'remembers' the environment in which it was created.
de developer.mozilla.org/en-US/docs/Web/JavaScript/Closureslet i = 0
lugar delvar i = 0
Ejemplo 5,testList()
imprimirá lo que desea originalmente.Cada función en JavaScript mantiene un enlace a su entorno léxico externo. Un entorno léxico es un mapa de todos los nombres (por ejemplo, variables, parámetros) dentro de un alcance, con sus valores.
Entonces, cada vez que vea la
function
palabra clave, el código dentro de esa función tiene acceso a las variables declaradas fuera de la función.Esto registrará
16
porque la función sebar
cierra sobre el parámetrox
y la variabletmp
, los cuales existen en el entorno léxico de la función externafoo
.La función
bar
, junto con su vínculo con el entorno léxico de la función,foo
es un cierre.Una función no tiene que volver para crear un cierre. Simplemente en virtud de su declaración, cada función se cierra sobre su entorno léxico que lo encierra, formando un cierre.
La función anterior también registrará 16, porque el código interno
bar
todavía puede referirse a argumentosx
y variablestmp
, a pesar de que ya no están directamente en el alcance.Sin embargo, dado
tmp
que todavía está dando vueltas dentrobar
del cierre, está disponible para ser incrementado. Se incrementará cada vez que llamebar
.El ejemplo más simple de un cierre es este:
Cuando se invoca una función de JavaScript,
ec
se crea un nuevo contexto de ejecución . Junto con los argumentos de la función y el objeto de destino, este contexto de ejecución también recibe un enlace al entorno léxico del contexto de ejecución de llamada, lo que significa que las variables declaradas en el entorno léxico externo (en el ejemplo anterior, ambosa
yb
) están disponibles enec
.Cada función crea un cierre porque cada función tiene un enlace a su entorno léxico externo.
Tenga en cuenta que las variables en sí son visibles desde un cierre, no copias.
fuente
delete
falla. Sin embargo, el entorno léxico que la función llevará a cabo como [[Alcance]] (y, en última instancia, utilizará como base para su propio entorno léxico cuando se invoque) se determina cuando se ejecuta la instrucción que define la función. Esto significa que la función se está cerrando sobre TODOS los contenidos del ámbito de ejecución, independientemente de los valores a los que realmente se refiere y si escapa del ámbito. Mire las secciones 13.2 y 10 en la especificaciónPRÓLOGO: esta respuesta se escribió cuando la pregunta era:
Estoy bastante seguro de que fui una de las únicas personas que intentó tomar la pregunta inicial literalmente. Desde entonces, la pregunta ha mutado varias veces, por lo que mi respuesta ahora puede parecer increíblemente tonta y fuera de lugar. Esperemos que la idea general de la historia siga siendo divertida para algunos.
Soy un gran admirador de la analogía y la metáfora al explicar conceptos difíciles, así que déjame probar suerte con una historia.
Érase una vez:
Había una princesa ...
Ella vivía en un mundo maravilloso lleno de aventuras. Conoció a su Príncipe Azul, cabalgó alrededor de su mundo en un unicornio, luchó contra dragones, encontró animales que hablaban y muchas otras cosas fantásticas.
Pero ella siempre tendría que regresar a su aburrido mundo de tareas y adultos.
Y a menudo les contaba su última aventura increíble como princesa.
Pero todo lo que verían es una niña ...
... contando historias sobre magia y fantasía.
Y a pesar de que los adultos sabían de princesas reales, nunca creerían en los unicornios o dragones porque nunca podrían verlos. Los adultos dijeron que solo existían dentro de la imaginación de la niña.
Pero sabemos la verdad real; que la niña con la princesa adentro ...
... es realmente una princesa con una niña dentro.
fuente
story()
función, que es la única interfaz que lalittleGirl
instancia expone al mundo de la magia.story
está el cierre, pero si el código hubiera sido,var story = function() {}; return story;
entonceslittleGirl
sería el cierre. Al menos esa es la impresión que obtengo del uso de MDN de métodos 'privados' con cierres : "Esas tres funciones públicas son cierres que comparten el mismo entorno".story
es un cierre que hace referencia al entorno proporcionado dentro del alcance deprincess
.princess
También es otro cierre implícito , es decir, elprincess
y ellittleGirl
compartirían cualquier referencia a unaparents
matriz que existiría en el entorno / ámbito dondelittleGirl
existe yprincess
se define el.princess
lo que está escrito. Desafortunadamente, esta historia ahora está un poco fuera de lugar en este hilo. Originalmente, la pregunta era "explicar los cierres de JavaScript a 5 años"; mi respuesta fue la única que incluso intentó hacerlo. No dudo que hubiera fallado miserablemente, pero al menos esta respuesta podría haber tenido la oportunidad de mantener el interés de un niño de 5 años.Tomando la pregunta en serio, deberíamos descubrir de qué es capaz cognitivamente un niño típico de 6 años, aunque es cierto que uno que está interesado en JavaScript no es tan típico.
Sobre el desarrollo infantil: 5 a 7 años dice:
Podemos usar este ejemplo para explicar los cierres, de la siguiente manera:
Podemos codificar esto en JavaScript así:
Otros puntos que explican por qué los cierres son interesantes:
makeKitchen()
se llama, se crea un nuevo cierre con su propio separadotrashBags
.trashBags
variable es local en el interior de cada cocina y no es accesible desde el exterior, pero la función interna de lagetTrashBag
propiedad tiene acceso a ella.getTrashBag
función hace eso aquí.fuente
El hombre de paja
Necesito saber cuántas veces se ha hecho clic en un botón y hacer algo cada tres clics ...
Solución bastante obvia
Ahora esto funcionará, pero invade el alcance externo al agregar una variable, cuyo único propósito es realizar un seguimiento del recuento. En algunas situaciones, esto sería preferible ya que su aplicación externa podría necesitar acceso a esta información. Pero en este caso, solo estamos cambiando el comportamiento de cada tercer clic, por lo que es preferible incluir esta funcionalidad dentro del controlador de eventos .
Considera esta opción
Observe algunas cosas aquí.
En el ejemplo anterior, estoy usando el comportamiento de cierre de JavaScript. Este comportamiento permite que cualquier función tenga acceso al ámbito en el que se creó, de forma indefinida. Para aplicar esto prácticamente, invoco inmediatamente una función que devuelve otra función, y debido a que la función que estoy devolviendo tiene acceso a la variable de recuento interno (debido al comportamiento de cierre explicado anteriormente), esto da como resultado un ámbito privado para el uso por el resultado función ... ¿No es tan simple? Vamos a diluirlo ...
Un simple cierre de una línea
Todas las variables fuera de la función devuelta están disponibles para la función devuelta, pero no están directamente disponibles para el objeto de la función devuelta ...
¿Consíguelo? Entonces, en nuestro ejemplo principal, la variable de conteo está contenida dentro del cierre y siempre está disponible para el controlador de eventos, por lo que conserva su estado de clic en clic.
Además, este estado de variable privada es totalmente accesible, tanto para las lecturas como para la asignación a sus variables de ámbito privado.
Ahí tienes ahora está encapsulando completamente este comportamiento.
Publicación de blog completa (incluidas las consideraciones de jQuery)
fuente
Los cierres son difíciles de explicar porque se utilizan para hacer que funcione un comportamiento que todos intuitivamente esperan que funcione de todos modos. Creo que la mejor manera de explicar ellos (y la forma en que me enteré de lo que hacen) es imaginar la situación sin ellos:
¿Qué pasaría aquí si JavaScript no supiera los cierres? Simplemente reemplace la llamada en la última línea por su cuerpo de método (que es básicamente lo que hacen las llamadas de función) y obtiene:
Ahora, ¿dónde está la definición de
x
? No lo definimos en el alcance actual. La única solución es dejarplus5
llevar su alcance (o más bien, el alcance de su padre). De esta manera,x
está bien definido y está vinculado al valor 5.fuente
TLDR
Un cierre es un enlace entre una función y su entorno léxico externo (es decir, tal como está escrito), de modo que los identificadores (variables, parámetros, declaraciones de funciones, etc.) definidos dentro de ese entorno son visibles desde dentro de la función, independientemente de cuándo o de donde se invoca la función.
Detalles
En la terminología de la especificación ECMAScript, se puede decir que un cierre se implementa mediante la
[[Environment]]
referencia de cada objeto de función, que apunta al entorno léxico dentro del cual se define la función.Cuando se invoca una función a través del
[[Call]]
método interno , la[[Environment]]
referencia en el objeto de función se copia en la referencia de entorno externo del registro de entorno del contexto de ejecución recién creado (marco de pila).En el siguiente ejemplo, la función se
f
cierra sobre el entorno léxico del contexto de ejecución global:En el siguiente ejemplo, la función se
h
cierra sobre el entorno léxico de la funcióng
, que, a su vez, se cierra sobre el entorno léxico del contexto de ejecución global.Si una función interna es devuelta por una externa, entonces el entorno léxico externo persistirá después de que la función externa haya regresado. Esto se debe a que el entorno léxico externo debe estar disponible si finalmente se invoca la función interna.
En el siguiente ejemplo, la función se
j
cierra sobre el entorno léxico de la funcióni
, lo que significa que la variablex
es visible desde la función internaj
, mucho después de que la funcióni
haya completado la ejecución:En un cierre, las variables en el entorno léxico exterior mismos están disponibles, no copias.
La cadena de entornos léxicos, vinculada entre contextos de ejecución a través de referencias de entornos externos, forma una cadena de alcance y define los identificadores visibles desde cualquier función dada.
Tenga en cuenta que, en un intento por mejorar la claridad y la precisión, esta respuesta ha cambiado sustancialmente con respecto a la original.
fuente
console.log
esa manera. Si alguien más está interesado, hay más: developer.mozilla.org/en-US/docs/DOM/…var
).OK, ventilador de cierres de 6 años. ¿Quieres escuchar el ejemplo más simple de cierre?
Imaginemos la siguiente situación: un conductor está sentado en un automóvil. Ese auto está dentro de un avión. El avión está en el aeropuerto. La capacidad del conductor para acceder a cosas fuera de su automóvil, pero dentro del avión, incluso si ese avión sale de un aeropuerto, es un cierre. Eso es. Cuando cumpla 27 años, mire la explicación más detallada o el ejemplo a continuación.
Aquí es cómo puedo convertir mi historia de avión en el código.
fuente
Este es un intento de aclarar varios (posibles) malentendidos sobre los cierres que aparecen en algunas de las otras respuestas.
fuente
Escribí una publicación de blog hace un tiempo explicando los cierres. Esto es lo que dije sobre los cierres en términos de por qué querrías uno.
En ese sentido, dejan que una función actúe un poco como un objeto con atributos privados.
Publicación completa:
Entonces, ¿qué son estas cosas de cierre?
fuente
devError = emailError("[email protected]", errorString)
y luego tener mi propia versión personalizada de una función compartida de emailError?Los cierres son simples:
El siguiente ejemplo simple cubre todos los puntos principales de los cierres de JavaScript. * *
Aquí hay una fábrica que produce calculadoras que pueden sumar y multiplicar:
El punto clave: cada llamada a
make_calculator
crea una nueva variable localn
, que sigue siendo utilizable por esa calculadoraadd
ymultiply
funciona mucho después de losmake_calculator
retornos.Si está familiarizado con los marcos de pila, estas calculadoras parecen extrañas: ¿cómo pueden seguir accediendo
n
después de lasmake_calculator
devoluciones? La respuesta es imaginar que JavaScript no usa "marcos de pila", sino que usa "marcos de montón", que pueden persistir después de la llamada a la función que los hizo regresar.Las funciones internas como
add
ymultiply
, que variables de acceso declaradas en una función externa ** , se denominan cierres .Eso es casi todo lo que hay para los cierres.
* Por ejemplo, cubre todos los puntos en el artículo "Cierres para tontos" que se da en otra respuesta , excepto el ejemplo 6, que simplemente muestra que las variables se pueden usar antes de que se declaren, un hecho agradable de conocer pero completamente ajeno a los cierres. También cubre todos los puntos en la respuesta aceptada , excepto los puntos (1) que las funciones copian sus argumentos en variables locales (los argumentos de la función nombrada), y (2) que copiar números crea un nuevo número, pero copiar una referencia de objeto le da otra referencia al mismo objeto. También es bueno saberlo, pero nuevamente no tiene nada que ver con los cierres. También es muy similar al ejemplo en esta respuesta, pero un poco más corto y menos abstracto. No cubre el punto deesta respuesta o este comentario , que es que JavaScript dificulta la conexión actualvalor de una variable de bucle en su función interna: el paso de "conexión" solo se puede hacer con una función auxiliar que encierra su función interna y se invoca en cada iteración del bucle. (Estrictamente hablando, la función interna accede a la copia de la variable de la función auxiliar, en lugar de tener algo enchufado). De nuevo, es muy útil cuando se crean cierres, pero no es parte de qué es un cierre o cómo funciona. Existe una confusión adicional debido a que los cierres funcionan de manera diferente en lenguajes funcionales como ML, donde las variables están vinculadas a valores en lugar de espacio de almacenamiento, proporcionando un flujo constante de personas que entienden los cierres de una manera (es decir, la forma de "enchufar") que es simplemente incorrecto para JavaScript, donde las variables siempre están vinculadas al espacio de almacenamiento y nunca a los valores.
** Cualquier función externa, si varias están anidadas, o incluso en el contexto global, como esta respuesta señala claramente.
fuente
first_calculator
es un objeto (no una función) no debe usar paréntesissecond_calculator = first_calculator;
, ya que es una asignación, no una llamada a la función. Para responder a su pregunta, solo habría una llamada a make_calculator, por lo que solo se haría una calculadora, y las variables first_calculator y second_calculator se referirían a la misma calculadora, por lo que las respuestas serían 3, 403, 4433, 44330.Cómo se lo explicaría a un niño de seis años:
¿Sabes cómo los adultos pueden ser dueños de una casa y la llaman hogar? Cuando una madre tiene un hijo, el niño realmente no posee nada, ¿verdad? Pero sus padres son dueños de una casa, por lo que cada vez que alguien le pregunta al niño "¿Dónde está tu casa?", Él / ella puede responder "¡esa casa!" Y señalar la casa de sus padres. Un "cierre" es la capacidad del niño de siempre (incluso si está en el extranjero) poder decir que tiene un hogar, aunque en realidad son los padres quienes poseen la casa.
fuente
¿Puedes explicar los cierres a un niño de 5 años? *
Todavía creo que la explicación de Google funciona muy bien y es conciso:
* AC # pregunta
fuente
Tiendo a aprender mejor mediante comparaciones BUENAS / MALAS. Me gusta ver el código de trabajo seguido de un código que no funciona que es probable que alguien encuentre. Monté un jsFiddle que hace una comparación y trata de reducirse las diferencias a las explicaciones más simples que pudiera ocurrir.
Cierres bien hechos:
En el código anterior
createClosure(n)
se invoca en cada iteración del bucle. Tenga en cuenta que nombré la variablen
para resaltar que es una nueva variable creada en un nuevo alcance de función y no es la misma variableindex
que está vinculada al alcance externo.Esto crea un nuevo alcance y
n
está vinculado a ese alcance; Esto significa que tenemos 10 ámbitos separados, uno para cada iteración.createClosure(n)
devuelve una función que devuelve la n dentro de ese alcance.Dentro de cada ámbito
n
está vinculado al valor que tenía cuandocreateClosure(n)
se invocó, por lo que la función anidada que se devuelve siempre devolverá el valorn
que tenía cuandocreateClosure(n)
se invocó.Cierres mal hechos:
En el código anterior, el bucle se movió dentro de la
createClosureArray()
función y la función ahora solo devuelve la matriz completa, que a primera vista parece más intuitiva.Lo que podría no ser obvio es que, dado que
createClosureArray()
solo se invoca una vez que se crea un solo alcance para esta función en lugar de uno para cada iteración del bucle.Dentro de esta función
index
se define una variable llamada . El ciclo se ejecuta y agrega funciones a la matriz que regresanindex
. Tenga en cuenta queindex
se define dentro de lacreateClosureArray
función que solo se invoca una vez.Debido a que solo había un alcance dentro de la
createClosureArray()
función,index
solo está vinculado a un valor dentro de ese alcance. En otras palabras, cada vez que el ciclo cambia el valor deindex
, lo cambia por todo lo que hace referencia dentro de ese alcance.Todas las funciones agregadas a la matriz devuelven la MISMA
index
variable del ámbito primario donde se definió en lugar de 10 diferentes de 10 ámbitos diferentes como el primer ejemplo. El resultado final es que las 10 funciones devuelven la misma variable del mismo ámbito.Después de que el ciclo terminó y
index
se modificó, el valor final fue 10, por lo tanto, cada función agregada a la matriz devuelve el valor de laindex
variable única que ahora se establece en 10.Resultado
fuente
let
paravar
arregla la diferencia.n
creadas en un nuevo cierre. Simplemente devolvemos una función para poder almacenarla en la matriz e invocarla más tarde.arr[index] = (function (n) { return 'n = ' + n; })(index);
. Pero luego está almacenando la cadena resultante en la matriz en lugar de una función para invocar que derrota el punto de mi ejemplo.Wikipedia sobre cierres :
Técnicamente, en JavaScript , cada función es un cierre . Siempre tiene acceso a variables definidas en el ámbito circundante.
Dado que la construcción que define el alcance en JavaScript es una función , no un bloque de código como en muchos otros lenguajes, lo que generalmente queremos decir con cierre en JavaScript es una función que trabaja con variables no locales definidas en una función circundante ya ejecutada .
Los cierres se usan a menudo para crear funciones con algunos datos privados ocultos (pero no siempre es así).
ems
El ejemplo anterior está utilizando una función anónima, que se ejecutó una vez. Pero no tiene que ser así. Puede nombrarse (p
mkdb
. Ej. ) Y ejecutarse más tarde, generando una función de base de datos cada vez que se invoca. Cada función generada tendrá su propio objeto de base de datos oculto. Otro ejemplo de uso de los cierres es cuando no devolvemos una función, sino un objeto que contiene múltiples funciones para diferentes propósitos, cada una de esas funciones tiene acceso a los mismos datos.fuente
Creé un tutorial interactivo de JavaScript para explicar cómo funcionan los cierres. ¿Qué es un cierre?
Aquí hay uno de los ejemplos:
fuente
Los secretos para las funciones de JavaScript son las variables privadas.
Cada vez que lo llama, se crea la variable local "nombre" y se le da el nombre "María". Y cada vez que sale la función, se pierde la variable y se olvida el nombre.
Como puede suponer, debido a que las variables se vuelven a crear cada vez que se llama a la función, y nadie más las conocerá, debe haber un lugar secreto donde se almacenan. Podría llamarse Cámara de los Secretos o pila o ámbito local, pero en realidad no importa. Sabemos que están allí, en algún lugar, escondidos en la memoria.
Pero, en JavaScript, existe esta cosa muy especial que las funciones que se crean dentro de otras funciones, también pueden conocer las variables locales de sus padres y mantenerlas mientras vivan.
Entonces, mientras estemos en la función principal, puede crear una o más funciones secundarias que comparten las variables secretas del lugar secreto.
Pero lo triste es que si el niño también es una variable privada de su función principal, también moriría cuando el padre terminara, y los secretos morirían con ellos.
Entonces, para vivir, el niño tiene que irse antes de que sea demasiado tarde
Y ahora, a pesar de que Mary "ya no está corriendo", el recuerdo de ella no se pierde y su hijo siempre recordará su nombre y otros secretos que compartieron durante su tiempo juntos.
Entonces, si llamas a la niña "Alice", ella responderá
Eso es todo lo que hay que contar.
fuente
No entiendo por qué las respuestas son tan complejas aquí.
Aquí hay un cierre:
Si. Probablemente lo uses muchas veces al día.
fuente
a
. En mi opinión, la sorpresa es que el objeto de alcance JS efectivamente proporcionaa
como una propiedad en lugar de una constante. Y solo notará ese comportamiento importante si lo modifica, como enreturn a++;
Ejemplo para el primer punto de dlaliberte:
fuente
Un cierre es donde una función interna tiene acceso a variables en su función externa. Esa es probablemente la explicación más simple de una línea que puede obtener para los cierres.
fuente
Sé que ya hay muchas soluciones, pero supongo que este script pequeño y simple puede ser útil para demostrar el concepto:
fuente
Estás durmiendo e invitas a Dan. Le dices a Dan que traiga un controlador XBox.
Dan invita a Paul. Dan le pide a Paul que traiga un controlador. ¿Cuántos controladores fueron llevados a la fiesta?
fuente
El autor de Closures ha explicado los cierres bastante bien, explicando la razón por la que los necesitamos y también explicando el entorno Lexical que es necesario para comprender los cierres.
Aquí está el resumen:
¿Qué pasa si se accede a una variable, pero no es local? Como aquí:
En este caso, el intérprete encuentra la variable en el
LexicalEnvironment
objeto externo .El proceso consta de dos pasos:
Cuando se crea una función, obtiene una propiedad oculta, llamada [[Scope]], que hace referencia al entorno Lexical actual.
Si se lee una variable, pero no se puede encontrar en ningún lado, se genera un error.
Funciones anidadas
Las funciones se pueden anidar una dentro de otra, formando una cadena de entornos Lexical que también se puede llamar una cadena de alcance.
Entonces, la función g tiene acceso a g, a y f.
Cierres
Una función anidada puede continuar viva después de que la función externa haya finalizado:
Marcado de entornos léxicos:
Como vemos,
this.say
es una propiedad en el objeto de usuario, por lo que continúa viva después de que el Usuario haya completado.Y si recuerda, cuando
this.say
se crea, (como cada función) obtiene una referencia internathis.say.[[Scope]]
al entorno Lexical actual. Por lo tanto, el entorno Lexical de la ejecución actual del usuario permanece en la memoria. Todas las variables de Usuario también son sus propiedades, por lo que también se guardan cuidadosamente, no se desechan como de costumbre.El objetivo es asegurarse de que si la función interna quiere acceder a una variable externa en el futuro, pueda hacerlo.
Para resumir:
Esto se llama cierre.
fuente
Las funciones de JavaScript pueden acceder a sus:
Si una función accede a su entorno, entonces la función es un cierre.
Tenga en cuenta que las funciones externas no son necesarias, aunque ofrecen beneficios que no discuto aquí. Al acceder a los datos en su entorno, un cierre mantiene esos datos vivos. En el caso de las funciones externas / internas, una función externa puede crear datos locales y eventualmente salir, y sin embargo, si alguna función interna sobrevive después de que la función externa sale, entonces la función interna mantiene los datos locales de la función externa. viva.
Ejemplo de un cierre que utiliza el entorno global:
Imagínese que los eventos de botón Stack Overflow Vote-Up y Vote-Down se implementan como cierres, voteUp_click y voteDown_click, que tienen acceso a variables externas isVotedUp y isVotedDown, que se definen globalmente. (En aras de la simplicidad, me refiero a los botones de votación de preguntas de StackOverflow, no a la matriz de botones de votación de respuestas).
Cuando el usuario hace clic en el botón VoteUp, la función voteUp_click verifica si isVotedDown == true para determinar si votar o simplemente cancelar un voto negativo. La función voteUp_click es un cierre porque está accediendo a su entorno.
Las cuatro funciones son cierres, ya que todos acceden a su entorno.
fuente
Como padre de un niño de 6 años, que actualmente enseña a niños pequeños (y un novato relativo a la codificación sin educación formal, por lo que se requerirán correcciones), creo que la lección se mantendrá mejor a través del juego práctico. Si el niño de 6 años está listo para entender qué es un cierre, entonces tiene la edad suficiente para intentarlo. Sugeriría pegar el código en jsfiddle.net, explicar un poco y dejarlos solos para inventar una canción única. El texto explicativo a continuación es probablemente más apropiado para un niño de 10 años.
INSTRUCCIONES
DATOS: Los datos son una colección de hechos. Pueden ser números, palabras, medidas, observaciones o incluso solo descripciones de cosas. No puedes tocarlo, olerlo o probarlo. Puedes escribirlo, hablarlo y escucharlo. Puede usarlo para crear olores y sabores táctiles usando una computadora. Puede ser útil por una computadora usando código.
CÓDIGO: Toda la escritura anterior se llama código . Está escrito en JavaScript.
JAVASCRIPT: JavaScript es un lenguaje. Al igual que el inglés o francés o chino son idiomas. Hay muchos idiomas que las computadoras y otros procesadores electrónicos entienden. Para que una computadora entienda JavaScript necesita un intérprete. Imagínese si un maestro que solo habla ruso viene a enseñar su clase en la escuela. Cuando el maestro dice "все садятся", la clase no lo entendería. Pero afortunadamente tienes un alumno ruso en tu clase que le dice a todos que esto significa "todos siéntense", así que todos lo hacen. La clase es como una computadora y el alumno ruso es el intérprete. Para JavaScript, el intérprete más común se llama navegador.
NAVEGADOR: cuando se conecta a Internet en una computadora, tableta o teléfono para visitar un sitio web, utiliza un navegador. Algunos ejemplos que puede conocer son Internet Explorer, Chrome, Firefox y Safari. El navegador puede entender JavaScript y decirle a la computadora lo que necesita hacer. Las instrucciones de JavaScript se llaman funciones.
FUNCIÓN: Una función en JavaScript es como una fábrica. Podría ser una pequeña fábrica con solo una máquina adentro. O puede contener muchas otras pequeñas fábricas, cada una con muchas máquinas que realizan diferentes trabajos. En una fábrica de ropa de la vida real, podría tener resmas de tela y bobinas de hilo entrando y camisetas y jeans saliendo. Nuestra fábrica de JavaScript solo procesa datos, no puede coser, perforar un agujero ni fundir metal. En nuestra fábrica de JavaScript, los datos entran y salen datos.
Todo este material de datos suena un poco aburrido, pero es realmente genial; podríamos tener una función que le diga a un robot qué hacer para la cena. Digamos que te invito a ti y a tu amigo a mi casa. Te gustan más las patas de pollo, me gustan las salchichas, tu amigo siempre quiere lo que quieres y mi amigo no come carne.
No tengo tiempo para ir de compras, por lo que la función necesita saber qué tenemos en el refrigerador para tomar decisiones. Cada ingrediente tiene un tiempo de cocción diferente y queremos que todo sea servido caliente por el robot al mismo tiempo. Necesitamos proporcionar a la función los datos sobre lo que nos gusta, la función podría "hablar" con el refrigerador y la función podría controlar el robot.
Una función normalmente tiene un nombre, paréntesis y llaves. Me gusta esto:
Tenga en cuenta eso
/*...*/
y//
detenga el código que lee el navegador.NOMBRE: puede llamar a una función casi cualquier palabra que desee. El ejemplo "cookMeal" es típico en unir dos palabras y darle al segundo una letra mayúscula al principio, pero esto no es necesario. No puede tener un espacio y no puede ser un número por sí solo.
PADRES: "Paréntesis" o
()
son el buzón en la puerta de la fábrica de funciones de JavaScript o un buzón en la calle para enviar paquetes de información a la fábrica. A veces, el buzón de correo puede estar marcado, por ejemplocookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime)
, en cuyo caso usted sabe qué datos debe proporcionar.BRAZOS: "Brackets" que se ven así
{}
son las ventanas tintadas de nuestra fábrica. Desde el interior de la fábrica se puede ver hacia afuera, pero desde afuera no se puede ver hacia adentro.EL EJEMPLO DE CÓDIGO LARGO ANTERIOR
Nuestro código comienza con la función de palabra , ¡así que sabemos que es uno! Luego, el nombre de la función sing : esa es mi propia descripción de lo que se trata la función. Luego paréntesis () . Los paréntesis siempre están ahí para una función. A veces están vacíos, ya veces tienen algo en Éste tiene una palabra en.:
(person)
. Después de esto hay un aparato ortopédico como este{
. Esto marca el inicio de la función sing () . Tiene un compañero que marca el final de sing () como este}
Entonces, esta función podría tener algo que ver con el canto, y podría necesitar algunos datos sobre una persona. Tiene instrucciones dentro para hacer algo con esos datos.
Ahora, después de la función sing () , cerca del final del código está la línea
VARIABLE: Las letras var significan "variable". Una variable es como un sobre. En el exterior, este sobre está marcado como "persona". En el interior contiene un trozo de papel con la información que necesita nuestra función, algunas letras y espacios unidos como un trozo de cuerda (se llama cadena) que forman una frase que dice "una anciana". Nuestro sobre podría contener otros tipos de cosas como números (llamados enteros), instrucciones (llamadas funciones), listas (llamadas matrices ). Debido a que esta variable se escribe fuera de todos los corchetes
{}
, y porque puede ver a través de las ventanas polarizadas cuando está dentro de los corchetes, esta variable se puede ver desde cualquier parte del código. Llamamos a esto una 'variable global'.VARIABLE GLOBAL: persona es una variable global, lo que significa que si cambia su valor de "una anciana" a "un hombre joven", la persona seguirá siendo un hombre joven hasta que decida cambiarlo nuevamente y que cualquier otra función en El código puede ver que es un hombre joven. Presione el F12botón o mire la configuración de Opciones para abrir la consola de desarrollador de un navegador y escriba "persona" para ver cuál es este valor. Escriba
person="a young man"
para cambiarlo y luego escriba "persona" nuevamente para ver si ha cambiado.Después de esto tenemos la línea.
Esta línea llama a la función, como si llamara a un perro
Cuando el navegador haya cargado el código JavaScript y haya alcanzado esta línea, iniciará la función. Puse la línea al final para asegurarme de que el navegador tenga toda la información que necesita para ejecutarlo.
Las funciones definen acciones: la función principal se trata de cantar. Contiene una variable llamada firstPart que se aplica al canto sobre la persona que se aplica a cada uno de los versos de la canción: "Hubo" + persona + "que tragó". Si escribe firstPart en la consola, no obtendrá una respuesta porque la variable está bloqueada en una función: el navegador no puede ver dentro de las ventanas polarizadas de las llaves.
CIERRES: Los cierres son las funciones más pequeñas que están dentro de la función grande sing () . Las pequeñas fábricas dentro de la gran fábrica. Cada uno tiene sus propias llaves, lo que significa que las variables dentro de ellas no se pueden ver desde el exterior. Es por eso que los nombres de las variables ( criatura y resultado ) pueden repetirse en los cierres pero con valores diferentes. Si escribe estos nombres de variables en la ventana de la consola, no obtendrá su valor porque está oculto por dos capas de ventanas polarizadas.
Todos los cierres saben qué es la variable de la función sing () llamada firstPart , porque pueden ver desde sus ventanas tintadas.
Después de los cierres vienen las líneas
La función sing () llamará a cada una de estas funciones en el orden en que se dan. Entonces se realizará el trabajo de la función sing ().
fuente
Bien, hablando con un niño de 6 años, posiblemente usaría las siguientes asociaciones.
Compare con una situación en la que una puerta estaba cerrada por un tiro y no había nadie adentro (ejecución de la función general), y luego se produce un incendio local y quema la habitación (recolector de basura: D), y luego se construyó una nueva habitación y ahora puede salir hay otros juguetes allí (nueva instancia de función), pero nunca se obtienen los mismos juguetes que se dejaron en la primera instancia de la sala.
Para un niño avanzado, pondría algo como lo siguiente. No es perfecto, pero te hace sentir lo que es:
Como puede ver, los juguetes que quedan en la habitación todavía son accesibles a través del hermano y no importa si la habitación está cerrada. Aquí hay un jsbin para jugar con él.
fuente
Una respuesta para un niño de seis años (suponiendo que sepa qué es una función y qué es una variable y qué datos son):
Las funciones pueden devolver datos. Un tipo de datos que puede devolver de una función es otra función. Cuando se devuelve esa nueva función, todas las variables y argumentos utilizados en la función que la creó no desaparecen. En cambio, esa función principal "se cierra". En otras palabras, nada puede mirar dentro de él y ver las variables que usó, excepto la función que devolvió. Esa nueva función tiene una capacidad especial para mirar hacia atrás dentro de la función que la creó y ver los datos dentro de ella.
Otra forma realmente simple de explicarlo es en términos de alcance:
Cada vez que crea un alcance más pequeño dentro de un alcance más grande, el alcance más pequeño siempre podrá ver lo que está dentro del alcance más grande.
fuente
Una función en JavaScript no es solo una referencia a un conjunto de instrucciones (como en el lenguaje C), sino que también incluye una estructura de datos ocultos que se compone de referencias a todas las variables no locales que utiliza (variables capturadas). Tales funciones de dos piezas se llaman cierres. Cada función en JavaScript puede considerarse un cierre.
Los cierres son funciones con un estado. Es algo similar a "esto" en el sentido de que "esto" también proporciona el estado para una función, pero la función y "esto" son objetos separados ("esto" es solo un parámetro sofisticado, y la única forma de vincularlo permanentemente a un La función es crear un cierre). Si bien "this" y la función siempre viven por separado, una función no puede separarse de su cierre y el lenguaje no proporciona medios para acceder a las variables capturadas.
Debido a que todas estas variables externas a las que hace referencia una función léxicamente anidada son en realidad variables locales en la cadena de sus funciones léxicamente envolventes (se puede suponer que las variables globales son variables locales de alguna función raíz), y cada ejecución de una función crea nuevas instancias de sus variables locales, se deduce que cada ejecución de una función que devuelve (o de lo contrario la transfiere, como registrarla como una devolución de llamada) una función anidada crea un nuevo cierre (con su propio conjunto potencialmente único de variables no locales referenciadas que representan su ejecución contexto).
Además, debe entenderse que las variables locales en JavaScript no se crean en el marco de la pila, sino en el montón y se destruyen solo cuando nadie hace referencia a ellas. Cuando una función regresa, las referencias a sus variables locales se reducen, pero aún pueden ser no nulas si durante la ejecución actual se convirtieron en parte de un cierre y todavía son referenciadas por sus funciones léxicamente anidadas (lo que puede suceder solo si las referencias a estas funciones anidadas se devolvieron o se transfirieron a algún código externo).
Un ejemplo:
fuente
Quizás un poco más allá de todos, pero el más precoz de los niños de seis años, pero algunos ejemplos que ayudaron a hacer que el concepto de cierre en JavaScript haga clic para mí.
Un cierre es una función que tiene acceso al alcance de otra función (sus variables y funciones). La forma más fácil de crear un cierre es con una función dentro de una función; La razón es que en JavaScript una función siempre tiene acceso al alcance de la función que lo contiene.
ALERTA: mono
En el ejemplo anterior, se llama a externalFunction, que a su vez llama a innerFunction. Tenga en cuenta que outsideVar está disponible para innerFunction, lo que se evidencia al alertar correctamente el valor de outsideVar.
Ahora considere lo siguiente:
ALERTA: mono
referenceToInnerFunction se establece en externalFunction (), que simplemente devuelve una referencia a innerFunction. Cuando se llama a referenceToInnerFunction, devuelve externalVar. De nuevo, como se indicó anteriormente, esto demuestra que innerFunction tiene acceso a externalVar, una variable de externalFunction. Además, es interesante observar que conserva este acceso incluso después de que externalFunction haya terminado de ejecutarse.
Y aquí es donde las cosas se ponen realmente interesantes. Si tuviéramos que deshacernos de externalFunction, digamos establecerlo en nulo, podría pensar que referenceToInnerFunction perdería su acceso al valor de outsideVar. Pero este no es el caso.
ALERTA: mono ALERTA: mono
¿Pero cómo es esto? ¿Cómo puede referenceToInnerFunction seguir conociendo el valor de outsideVar ahora que externalFunction se ha establecido en nulo?
La razón por la que referenceToInnerFunction aún puede acceder al valor de outsideVar es porque cuando el cierre se creó por primera vez colocando innerFunction dentro de outsideFunction, innerFunction agregó una referencia al alcance de externalFunction (sus variables y funciones) a su cadena de alcance. Lo que esto significa es que innerFunction tiene un puntero o una referencia a todas las variables de externalFunction, incluida externalVar. Entonces, incluso cuando externalFunction haya terminado de ejecutarse, o incluso si se elimina o se establece en nulo, las variables en su alcance, como externalVar, permanecen en la memoria debido a la referencia sobresaliente a ellas por parte de innerFunction que se ha devuelto a referenceToInnerFunction. Para liberar de la memoria a externalVar y al resto de las variables de externalFunction, tendría que deshacerse de esta excelente referencia a ellas,
//////////
Otras dos cosas sobre los cierres a tener en cuenta. Primero, el cierre siempre tendrá acceso a los últimos valores de su función de contención.
ALERTA: gorila
Segundo, cuando se crea un cierre, retiene una referencia a todas las variables y funciones de su función de cierre; no puede escoger y elegir. Y, sin embargo, los cierres deben usarse con moderación, o al menos con cuidado, ya que pueden requerir mucha memoria; Se pueden guardar muchas variables en la memoria mucho después de que una función que contiene haya terminado de ejecutarse.
fuente
Simplemente los señalaría a la página de cierres de Mozilla . Es la mejor, más concisa y simple explicación de los conceptos básicos de cierre y uso práctico que he encontrado. Es muy recomendable para cualquiera que esté aprendiendo JavaScript.
Y sí, incluso lo recomendaría a un niño de 6 años: si el niño de 6 años está aprendiendo acerca de los cierres, entonces es lógico que estén listos para comprender la explicación concisa y simple proporcionada en el artículo.
fuente