En pocas palabras, los cierres de Javascript permiten que una función acceda a una variable que se declara en una función léxica-primaria .
Veamos una explicación más detallada. Para comprender los cierres, es importante comprender cómo JavaScript define las variables.
Alcances
En JavaScript, los ámbitos se definen con funciones. Cada función define un nuevo alcance.
Considere el siguiente ejemplo;
function f()
{//begin of scope f
var foo='hello'; //foo is declared in scope f
for(var i=0;i<2;i++){//i is declared in scope f
//the for loop is not a function, therefore we are still in scope f
var bar = 'Am I accessible?';//bar is declared in scope f
console.log(foo);
}
console.log(i);
console.log(bar);
}//end of scope f
llamando f impresiones
hello
hello
2
Am I Accessible?
Consideremos ahora el caso en que tenemos una función g
definida dentro de otra función f
.
function f()
{//begin of scope f
function g()
{//being of scope g
/*...*/
}//end of scope g
/*...*/
}//end of scope f
Llamaremos f
al padre léxico de g
. Como se explicó antes, ahora tenemos 2 ámbitos; El alcance f
y el alcance g
.
Pero un alcance está "dentro" del otro alcance, entonces, ¿el alcance de la función secundaria es parte del alcance de la función primaria? Lo que sucede con las variables declaradas en el alcance de la función padre; ¿podré acceder a ellos desde el alcance de la función secundaria? Ahí es exactamente donde intervienen los cierres.
Cierres
En JavaScript, la función g
no solo puede acceder a ninguna variable declarada en el alcance, g
sino también a cualquier variable declarada en el alcance de la función principal f
.
Considera seguir;
function f()//lexical parent function
{//begin of scope f
var foo='hello'; //foo declared in scope f
function g()
{//being of scope g
var bar='bla'; //bar declared in scope g
console.log(foo);
}//end of scope g
g();
console.log(bar);
}//end of scope f
llamando f impresiones
hello
undefined
Miremos la línea console.log(foo);
. En este punto, estamos dentro del alcance g
e intentamos acceder a la variable foo
que se declara dentro del alcance f
. Pero como se indicó anteriormente, podemos acceder a cualquier variable declarada en una función principal léxica, que es el caso aquí; g
es el padre léxico de f
. Por hello
lo tanto está impreso.
Veamos ahora la línea console.log(bar);
. En este punto, estamos dentro del alcance f
e intentamos acceder a la variable bar
que se declara dentro del alcance g
. bar
no se declara en el ámbito actual y la función g
no es la principal f
, por bar
lo tanto, no está definida
En realidad, también podemos acceder a las variables declaradas en el ámbito de una función léxica "gran padre". Por lo tanto, si hubiera una función h
definida dentro de la funcióng
function f()
{//begin of scope f
function g()
{//being of scope g
function h()
{//being of scope h
/*...*/
}//end of scope h
/*...*/
}//end of scope g
/*...*/
}//end of scope f
entonces h
sería capaz de acceder a todas las variables declaradas en el ámbito de la función h
, g
y f
. Esto se hace con cierres . En JavaScript, los cierres nos permiten acceder a cualquier variable declarada en la función léxica principal, en la función léxica principal, en la función léxica principal, etc. Esto puede verse como una cadena de alcance ; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ...
hasta la última función padre que no tiene padre léxico.
El objeto ventana
En realidad, la cadena no se detiene en la última función principal. Hay un alcance especial más; El alcance global . Cada variable no declarada en una función se considera declarada en el ámbito global. El alcance global tiene dos especialidades;
- todas las variables declaradas en el alcance global son accesibles desde cualquier lugar
- Las variables declaradas en el ámbito global corresponden a las propiedades del
window
objeto.
Por lo tanto, hay exactamente dos formas de declarar una variable foo
en el ámbito global; ya sea al no declararlo en una función o al establecer la propiedad foo
del objeto de ventana.
Ambos intentos usan cierres
Ahora que ha leído una explicación más detallada, puede ser evidente que ambas soluciones usan cierres. Pero para estar seguros, hagamos una prueba.
Creemos un nuevo lenguaje de programación; JavaScript sin cierre. Como su nombre indica, JavaScript-No-Closure es idéntico a JavaScript, excepto que no admite Closures.
En otras palabras;
var foo = 'hello';
function f(){console.log(foo)};
f();
//JavaScript-No-Closure prints undefined
//JavaSript prints hello
Muy bien, veamos qué sucede con la primera solución con JavaScript-No-Closure;
for(var i = 0; i < 10; i++) {
(function(){
var i2 = i;
setTimeout(function(){
console.log(i2); //i2 is undefined in JavaScript-No-Closure
}, 1000)
})();
}
por lo tanto, esto se imprimirá undefined
10 veces en JavaScript-No-Closure.
Por lo tanto, la primera solución utiliza el cierre.
Veamos la segunda solución;
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2); //i2 is undefined in JavaScript-No-Closure
}
})(i), 1000);
}
por lo tanto, esto se imprimirá undefined
10 veces en JavaScript-No-Closure.
Ambas soluciones utilizan cierres.
Editar: se supone que estos 3 fragmentos de código no están definidos en el ámbito global. De lo contrario, las variables foo
y se i
unirían al window
objeto y, por lo tanto, serían accesibles a través del window
objeto en JavaScript y JavaScript-No-Closure.