paginas - javascript tutorial
¿JavaScript no admite cierres con variables locales? (7)
Creo que esto es lo que quieres:
var closures = [];
function createClosure(i) {
closures[i] = function() {
alert("i = " + i);
};
}
function create() {
for (var i = 0; i < 5; i++) {
createClosure(i);
}
}
Esta pregunta ya tiene una respuesta aquí:
Estoy muy confundido acerca de este código:
var closures = [];
function create() {
for (var i = 0; i < 5; i++) {
closures[i] = function() {
alert("i = " + i);
};
}
}
function run() {
for (var i = 0; i < 5; i++) {
closures[i]();
}
}
create();
run();
Desde mi punto de vista, debería imprimir 0,1,2,3,4 (¿no es este el concepto de cierres?).
En cambio, imprime 5,5,5,5,5.
Intenté con Rhino y Firefox.
¿Podría alguien explicarme este comportamiento? Thx por adelantado.
El JavaScript avanzado de aprendizaje de John Resig explica esto y más. Es una presentación interactiva que explica mucho sobre JavaScript, y los ejemplos son divertidos de leer y ejecutar.
Tiene un capítulo sobre cierres, y este ejemplo se parece mucho al tuyo.
Aquí está el ejemplo roto:
var count = 0;
for ( var i = 0; i < 4; i++ ) {
setTimeout(function(){
assert( i == count++, "Check the value of i." );
}, i * 200);
}
Y la solución:
var count = 0;
for ( var i = 0; i < 4; i++ ) (function(i){
setTimeout(function(){
assert( i == count++, "Check the value of i." );
}, i * 200);
})(i);
Esto es lo que debe hacer para lograr su resultado:
<script>
var closures = [];
function create() {
for (var i = 0; i < 5; i++) {
closures[i] = function(number) {
alert("i = " + number);
};
}
}
function run() {
for (var i = 0; i < 5; i++) {
closures[i](i);
}
}
create();
run();
</script>
La solución es tener una lambda autoejecutable que envuelva su inserción de matriz. También me pasa como un argumento a esa lambda. El valor de i dentro de la lambda autoejecutable sombreará el valor de la i original y todo funcionará según lo previsto:
function create() {
for (var i = 0; i < 5; i++) (function(i) {
closures[i] = function() {
alert("i = " + i);
};
})(i);
}
Otra solución sería crear otro cierre que capture el valor correcto de i y lo asigne a otra variable que quedaría "atrapada" en la lambda final:
function create() {
for (var i = 0; i < 5; i++) (function() {
var x = i;
closures.push(function() {
alert("i = " + x);
});
})();
}
Sí, los cierres funcionan aquí. Cada vez que haces un bucle, la función que estás creando atrapa el i
. Cada función que creas comparte lo mismo i
. El problema que está viendo es que, dado que todos comparten el mismo i
, también comparten el valor final de i
ya que es la misma variable capturada.
Editar: Este artículo del Sr. Skeet explica los cierres con cierta profundidad y aborda este tema en particular de una manera que es mucho más informativa que la que tengo aquí. Sin embargo, tenga cuidado ya que la forma en que Javascript y C # manejan cierres tienen algunas diferencias sutiles. Vaya a la sección llamada "Comparación de las estrategias de captura: complejidad versus poder" para obtener una explicación sobre este tema.
Se corrigió la respuesta de Jon al agregar una función anónima adicional:
function create() {
for (var i = 0; i < 5; i++) {
closures[i] = (function(tmp) {
return function() {
alert("i = " + tmp);
};
})(i);
}
}
La explicación es que los ámbitos de JavaScript son a nivel de función, no a nivel de bloque, y la creación de un cierre solo significa que el ámbito adjunto se agrega al entorno léxico de la función adjunta.
Después de que termina el bucle, la variable de nivel de función i
tiene el valor 5
, y eso es lo que la función interna "ve".
Como nota al margen: debe tener cuidado con la creación innecesaria de objetos de función, especialmente en bucles; es ineficiente, y si se trata de objetos DOM, es fácil crear referencias circulares y, por lo tanto, introducir fugas de memoria en Internet Explorer.
Simplemente definiendo una función interna, o asignándola a alguna variable:
closures[i] = function() {...
no crea una copia privada de todo el contexto de ejecución. El contexto no se copia hasta que la función exterior más cercana está saliendo (en ese punto esas variables externas podrían ser recogidas basura, así que será mejor que tomemos una copia).
Esta es la razón por la que funciona otra función alrededor de tu función interna: el intermediario realmente ejecuta y sale, marca la función más interna para guardar su propia copia de la pila.