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 gdefinida 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 fal padre léxico de g. Como se explicó antes, ahora tenemos 2 ámbitos; El alcance fy 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 gno solo puede acceder a ninguna variable declarada en el alcance, gsino 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 ge intentamos acceder a la variable fooque 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í; ges el padre léxico de f. Por hellolo tanto está impreso.
Veamos ahora la línea console.log(bar);. En este punto, estamos dentro del alcance fe intentamos acceder a la variable barque se declara dentro del alcance g. barno se declara en el ámbito actual y la función gno es la principal f, por barlo 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 hdefinida 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 hsería capaz de acceder a todas las variables declaradas en el ámbito de la función h, gy 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
windowobjeto.
Por lo tanto, hay exactamente dos formas de declarar una variable fooen el ámbito global; ya sea al no declararlo en una función o al establecer la propiedad foodel 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á undefined10 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á undefined10 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 fooy se iunirían al windowobjeto y, por lo tanto, serían accesibles a través del windowobjeto en JavaScript y JavaScript-No-Closure.