I have tried to explain closures in JavaScript to a friend of 27 years but failed completely.
How do you explain the concept to someone with knowledge that is part of closures (such as functions and variables) but doesn't understand closures themselves?
The complexity of explaining the concept of a closure is that it requires an understanding of other fundamental concepts such as lexical scope, free variables, and function evaluation environments. It is also essential to understand the difference between evaluating a function (the function definition itself) and evaluating an invocation of it (performing an execution of the function).
Perhaps it is better to try to understand the closure as a concept, free from the implementation details of any language.
I will do my best to explain it as I understand it as simply as possible.
To understand closures, you have to think like a language interpreter during its process of evaluating a function invocation.
Consider the following example:
Thinking first about the static and declarative aspect of the function, in the example above, the variable
nombre
is what is known as a free variable , that is, a variable that is not part of the arguments of the function, nor was it declared inside from his body. Apart from this variable, our function also uses declared variables as its arguments (for example:saludo
).The variable is therefore expected to
nombre
be within the lexical scope of our function. That is, it has been declared prior to our function declarationsaludar
(don't let JavaScript hoisting fool you on this).Let's now leave the compile time aside and look at things from the point of view of the runtime.
If we were an interpreter of the language, when evaluating an invocation of our function, for example
saludar("Hola")
, we are going to need two things:An environment containing the values of all variables in the lexical scope of the function ( bindings ).
The code of the function itself.
That's basically a closure, the combination of these two things.
Imagine that, being the interpreter of a language like JavaScript , you had access, when evaluating this invocation at runtime, to a hypothetical structure like the following, representing the closure of our invocation:
When evaluating the function, when the interpreter comes across the variable
saludo
, it looks for it in the environment of the closure, when it comes across the variablenombre
, it looks for it in the environment of the closure and at the end it can evaluate the expressionsaludo + ", " + nombre
.In other words, the environment is a dictionary containing any names/identifiers/bindings used within the function itself (variables in use of its lexical scope).
So a closure is the function itself, plus an environment containing its bindings to the free variables in the function's lexical scope, the bindings to the function's arguments, and often a pointer or reference to the function itself, in case it is a recursive function (in which case it will use an identifier to represent itself and thus requires a binding in the environment, which is what the interpreter uses to resolve any names it needs to evaluate to in the body of the function).
As you can see, this does not require the closure function to be declared inside another function. It simply requires that it have access to free variables in its lexical scope. However, given the natural properties of lexical scope (for example: where the innermost scope has access to variables in all ancestor scopes, but not the opposite) it is common to use closures as a form of encapsulation, as in the example given in the other answer to this question.
Closures are not difficult to understand.
Assume the following code, the simplest example I can think of.
Here you are creating a closure. You create it when you declare a function inside another one that uses variables from the outer function . They are the variables
acumulado
andvalor
those that are closed in the functionquintos
. And this "closure" will stay that way as long as the internally declared function lives.To remove the closure, simply remove the reference to the new function.
Ultimately, even after the execution of
saltos(5)
, JavaScript keeps a reference to the variables declared insaltos
(accumulated and value), visible to the function created inside.If you're interested in the internals of how JavaScript maintains these references, there are some good answers that include a lot of internals. My intention was to answer the original question, to explain closures using simple terms like variable and function.
Puesto que el OP asume que sabemos lo que es una función y variables, construyamos cosas con esos dos elementos:
Imaginemos que tenemos un programa en el que se ha declarado una función
Para simplificar mucho este escenario asumamos que esta función es una caja negra: no afecta a nada externo y se ve afectada por nada externo a su código y sus parámetros. Es la típica función predecible que siempre nos va a dar la misma salida para los mismos parámetros de entrada.
Pero ¿y si necesitamos que el resultado varíe? ¿Y si necesitamos, por ejemplo, que la función recuerde que ya ha sido llamada?
La manera más burda de solucionar este requerimiento sería crear una -horror- variable global. No vamos a entrar en por qué esta solución no es elegante ni recomendable, aceptemos que es una mala idea. Queremos algo más robusto, una variable que nadie más que esa función pueda tocar.
Aquí aparecen las clausuras de funciones. Este primer detalle es importante: el nombre completo es clausura, cierre o cerradura de función: vamos a tomar una función y encerrarla en un entorno propio que va a incluir a la función y a otras cosas. Este entorno será... otra función. Es decir, tendremos una función declarada dentro de otra. Esto significa que cada vez que llamemos a la función externa, se generará la función interna, al igual que las variables locales de una función normal:
Como puedes ver, la función interna
funcionEncerrada
es lo que devolvemos, por lo que su existencia no termina cuando la función externa se completa. Y, puesto que hace uso de una variable local de la función externa (en este casovariableOculta
), ésta tampoco puede ser descartada. Hemos creado un entorno cerrado, solo visible para la función interna. Veámoslo en funcionamiento:Desde fuera es imposible acceder a la
variableOculta
porque fue declarada como variable local de la funcióngenerarClausura
, pero para lafuncionEncerrada
se comporta como una variable global: está fuera de ella misma pero no ha desaparecido al finalizar la ejecución de ninguna de las dos funciones.Para terminar, te pongo un ejemplo práctico pero general del uso de clausuras: Simular atributos privados en un objeto.
Primero veamos la construcción clásica para ver la limitación de Javascript: no existe el concepto de atributo privado
Ahora simulemos esta privacidad con clausuras:
Incluso aunque creemos un atributo en el objeto, este será ignorado porque realmente nuestros métodos set y get trabajan con la variable encerrada, que está fuera del alcance del código que no se haya escrito dentro de la función constructor.
Let's see... where do we start... I think at the beginning.
We all know what a program is, right?
We can still refine further:
Okay. Easy, isn't it? Now, let's focus on functions :
Note that the local data is not part of the function; they are things external to it. Ok, so far it's simple. A little closer...
It's still simple... every time a function is called, it starts executing from the first statement , which makes all the logic in the world :-)
But in Javascript (and many other languages, for that matter), it is possible to define functions within functions .
Voucher. Now, let's make a little change...
We simply remove the inner function and replace it with statements ... putting a
return
in front of it, to make sure we never get there. Note that, in reality, nothing has been changed ; we cannot access the internal function from outside , and, from inside the function, it does not interfere either (there is areturn
right in front).Voucher. Now... what if we could somehow get the address of
instrucción1-1
out of that function ? What if, in addition, we were able to enter directly to that address ? We can use aswitch( )
for this, changing things very little :now we can do
which will execute
But we could also do
which will execute
Through this simple change in our perspective of the function concept , we have managed not to be limited to always starting the execution from the beginning: we can choose which part (which
function
internal) we want to execute .Well, we already have a first part of what a closure is : an entry point to a function, which does not correspond to its normal entry point .
And what about
variable1
andvariable2
?Well, this is where things get a bit complicated :-)
Let's make a modification to how a function is called:
Instead, let's imagine that, in reality, this other thing is done :
and we call it like this:
That 's a closure :
At this point, it is possible to start execution at any one of 3 points:
begin
,subfunction1
andsubfunction2
. Which is equivalent to:And, in addition, we keep the value of the variables between different calls, since said value is external to the code of the function itself .
By doing so, there are a number of properties:
There can be multiple closures of the same function ; each closure has its own private copy of the data and its own entry point.
Se tratan como cualquier otro objeto: su tiempo de vida está determinado por su propio contador de referencias; cuando no hay referencias a la closure, esta se elimina.
Al igual que cualquier otro objeto, no se eliminan mientras que tengan referencias ... por ejemplo, al ser usadas para callbacks.
To understand closures I'm going to assume you understand the basic features of the language that make them possible:
When a function returns another function, the second one keeps the references to the variables that were defined in the first one.
Closure is that ability to maintain references, or encompass them, or enclose them.
Surprise!! By calling function B from the global scope, B can access variables in A that are not accessible from the global scope.
Aprovecho este espacio para ofrecer una versión traducida a nuestro idioma de una de las respuestas que refieres en tu pregunta, la cual tiene como base el excelente artículo de Morris sobre Closures titulado JavaScript Closures for Dummies.
No sé si esto pueda ser entendido por un niño de 6 años, pero puede ser al menos un comienzo para público de todas las edades :).
Dado que allí la respuesta ha sido marcada como Wiki de comunidad, respetamos la intención del autor, y la marcamos también aquí como Wiki.
Las clausuras no son mágicas
Esta página explica las clausuras para que un programador pueda entenderlas, utilizando el código JavaScript de trabajo. No es para gurús ni para programadores funcionales.
Las clausuras no son difíciles de entender una vez que el concepto de base se ha desarrollado. Sin embargo, ¡son imposibles de entender al leer cualquier artículo académico o información académica sobre ellas!
Este artículo está dirigido a programadores con cierta experiencia en programación en un lenguaje de flujo principal, y que pueden leer la siguiente función de JavaScript:
Dos resúmenes breves
Cuando una función (
foo
) declara otras funciones (bar
ybaz
), la familia de variables locales creadas enfoo
no se destruye cuando la función sale. Las variables simplemente se vuelven invisibles para el mundo exterior. Por lo tanto,foo
puede devolver astutamente las funcionesbar
ybaz
, y éstas pueden continuar leyendo, escribiendo y comunicándose entre sí a través de esta familia cerrada de variables ("la clausura") con la que nadie más puede entrometerse, ni siquiera alguien que llame afoo
otra vez en el futuro.Una clausura es una forma de apoyar funciones de primera clase; es una expresión que puede hacer referencia a variables dentro de su alcance (cuando se declaró por primera vez), asignarse a una variable, pasarse como argumento a una función o devolverse como resultado de una función.
Un ejemplo de clausura
El siguiente código devuelve una referencia a una función:
La mayoría de los programadores de JavaScript entenderán cómo una referencia a una función se devuelve a una variable (
say2
) en el código anterior. Si no lo hace, entonces necesita comprender eso antes de que pueda aprender acerca de las clausuras. Un programador que usa C pensaría que la función devolvía un puntero a una función, y que las variablessay
ysay2
eran cada una un puntero a una función.Hay una diferencia crítica entre un puntero C a una función y una referencia de JavaScript a una función. En JavaScript, se puede pensar que una variable de referencia de función tiene tanto un puntero a una función como un puntero oculto a una clausura.
El código anterior tiene una clausura porque la función anónima
function() { console.log(text); }
se declara dentro de otra función,sayHello2 ()
en este ejemplo. En JavaScript, si utilizas la palabra clavefunction
dentro de otra función, estás creando una clausura.En C y en la mayoría de los otros lenguajes comunes, después de que una función retorna, todas las variables locales ya no son accesibles porque se destruye el marco de pila.
En JavaScript, si declaras una función dentro de otra función, las variables locales de la función externa pueden permanecer accesibles después de que la función haya retornado. Esto se demuestró anteriormente, porque llamamos a la función
say2 ()
después de haber retornado desayHello2 ()
. Observa que el código que llamamos hace referencia a la variabletext
, que era una variable local de la funciónsayHello2 ()
.Si observamos la salida de
say2.toString ()
, podemos ver que el código se refiere a la variabletext
. La función anónima puede hacer referencia al texto que contiene el valor 'Hola Bob' porque las variables locales desayHello2 ()
se han mantenido vivas secretamente en la clausura.La clave es que en JavaScript una referencia de función también tiene una referencia secreta a la clausura en la que se creó, de manera similar a como los delegados son un puntero a un método más una referencia secreta a un objeto.
Más ejemplos
Por alguna razón, las clausuras parecen ser muy difíciles de entender cuando lees sobre ellas, pero cuando ves algunos ejemplos, queda claro cómo funcionan (me tomó un tiempo). Recomiendo trabajar con los ejemplos cuidadosamente hasta que entiendas cómo funcionan. Si comienzas a usar clausuras sin entender completamente cómo funcionan, ¡pronto crearías algunos errores muy extraños!
Ejemplo 3
Este ejemplo muestra que las variables locales no se copian, se mantienen por referencia. ¡Es como si el marco de pila se mantuviera vivo en la memoria incluso después de que existe la función externa!
Ejemplo 4
Las tres funciones globales tienen una referencia común al mismo cierre porque todas se declaran dentro de una sola llamada a
setupSomeGlobals()
.Las tres funciones tienen acceso compartido a la misma clausura: las variables locales de
setupSomeGlobals()
cuando se definieron las tres funciones.Ten en cuenta que en el ejemplo anterior, si llamas a
setupSomeGlobals()
nuevamente, se creará un nuevo cierre (stack-frame!). Las antiguas variablesgLogNumber
,gIncreaseNumber
,gSetNumber
se sobrescriben con nuevas funciones que tienen la nueva clausura. (En JavaScript, cada vez que declaras una función dentro de otra función, las funciones internas se recrean nuevamente cada vez que se llama a la función externa).Ejemplo 5
Este ejemplo muestra que la clausura contiene variables locales que se declararon dentro de la función externa antes de salir. Ten en cuenta que la variable
alice
se declara realmente después de la función anónima. La función anónima se declara primero, y cuando se llama a esa función, puedes acceder a la variablealice
porquealice
está en el mismo ámbito (JavaScript hace la elevación de variables). TambiénsayAlice()()
simplemente llama directamente a la referencia de función devuelta porsayAlice()
, es exactamente lo mismo que se hizo anteriormente pero sin la variable temporal.Difícil: también debes tener en cuenta que la variable
say
también está dentro de la clausura, y puede accederse a ella mediante cualquier otra función que pueda declararse dentro desayAlice()
, o puede accederse recursivamente dentro de la función que está dentro.Ejemplo 6
Este es un verdadero problema para muchas personas, por lo que necesitas entenderlo. Ten mucho cuidado si estás definiendo una función dentro de un bucle: es posible que las variables locales de la clausura no actúen como podrías pensar.
Necesitas entender la característica de "elevación de variables" en Javascript para entender este ejemplo.
La línea
result.push (function () {console.log (item + '' + list [i])}
agrega una referencia a una función anónima tres veces en la matriz de resultados. Si no estás tan familiarizado con las funciones anónimas, piense que es como:¡Ten en cuenta que cuando ejecutas el ejemplo, "item2 undefined" se registra tres veces! Esto se debe a que, al igual que en los ejemplos anteriores, solo hay una clausura para las variables locales para
buildList
(que son resultado,i
yitem
). Cuando las funciones anónimas se llaman en la líneafnlist [j]()
; todos utilizan la misma clausura única, y usan el valor actual parai
y paraitem
dentro de esa clausura (dondei
tiene un valor de 3 porque el bucle se había completado, yitem
tiene un valor de 'item2'). Ten en cuenta que estamos indexando desde 0, por lo tanto,item
tiene un valor de item2. Y eli++
incrementarái
al valor 3.Puede ser útil ver qué sucede cuando se utiliza una declaración a nivel de bloque de la variable
item
(a través de la palabra clavelet
) en lugar de una declaración de variable con ámbito de función a través de la palabra clavevar
. Si se realiza ese cambio, entonces cada función anónima en el resultado de la matriz tiene su propia clausura. Cuando se ejecuta el ejemplo, la salida es la siguiente:Si la variable
i
también se define utilizandolet
en lugar devar
, entonces la salida es:Ejemplo 7
En este ejemplo final, cada llamada a la función principal crea una clausura separada.
Sumario
Si todo parece completamente incierto, entonces lo mejor es jugar con los ejemplos. Leer una explicación es mucho más difícil de entender que los ejemplos. Mis explicaciones de clausuras y apilamientos, etc. no son técnicamente correctas, son simplificaciones generales que pretenden ayudar a comprender. Una vez que la idea básica ha sido asimilada, puedes recoger los detalles más adelante.
Puntos finales:
Cuando se utiliza
function
dentro de otra función, se utiliza una clausura.Siempre que uses
eval()
dentro de una función, se usa una clausura. El texto que se evalúa puede hacer referencia a las variables locales de la función, y dentro deeval
incluso puedes crear nuevas variables locales utilizandoeval ('var foo = ...')
Cuando utilizas
new function (...)
(el constructor de funciones) dentro de una función, no creas una clausura. (La nueva función no puede hacer referencia a las variables locales de la función externa).Una clausura en JavaScript es como mantener una copia de todas las variables locales, tal como eran cuando una función salía (
exit
).Probablemente es mejor pensar que en una clausura siempre se crea solo una entrada a una función, y las variables locales se agregan a esa clausura.
Se mantiene un nuevo conjunto de variables locales cada vez que se llama a una función con una clausura (dado que la función contiene una declaración de función dentro de ella, y se devuelve una referencia a esa función interna o se mantiene una referencia externa de alguna manera).
Dos funciones pueden parecer que tienen el mismo texto de origen, pero tienen un comportamiento completamente diferente debido a su clausura "oculta". No creo que el código JavaScript pueda realmente averiguar si una referencia de función tiene una clausura o no.
Si estás intentando realizar modificaciones dinámicas al código fuente (por ejemplo:
myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));)
, no funcionará simyFunction
es una clausura ( por supuesto, nunca se le ocurriría hacer una sustitución de cadena de código fuente en tiempo de ejecución, pero ...).Es posible obtener declaraciones de funciones dentro de las declaraciones de funciones dentro de las funciones y &mdash, y puedes obtener clausuras en más de un nivel.
Creo que normalmente una clausura es un término para la función junto con las variables que se capturan. ¡Ten en cuenta que no uso esa definición en este artículo!
Sospecho que las clausuras en JavaScript difieren de las que se encuentran normalmente en los lenguajes funcionales.
Enlaces
La forma más simple de explicarlo es:
Las clausuras no son más que una función que vive dentro de otra función.
Es decir, imaginemos una función
casa
, al llamar a esta nos devolverá el número de la casa, pero dentro de la casa podemos tenerpersonas
y al llamar a esta casa porx
persona nos responderá siempre alguien, pero aquí es la magia, no es posible llamar a una persona sin antes ir a su casa!¿Cómo querer llamar a alguien si no se tiene su número telefónico o móvil? Pero podemos ir a la casa y luego llamar a la persona! De lo contrario, conseguir su número y luego llamar a esta!
Ahora bien, vamos a expandir el ejemplo preguntando por una persona:
Creamos máquinas
Créditos de íconos: robot, teclado, portátil y ordenador:
Icons made by Dave Gandy from www.flaticon.com is licensed by CC 3.0 BY; Icons made by SimpleIcon from www.flaticon.com is licensed by CC 3.0 BY; Icons made by Eucalyp from www.flaticon.com is licensed by CC 3.0 BY