2009-03-13 22 views
51

Estoy muy confundido acerca de este código:¿JavaScript no admite cierres con variables locales?

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 entender debe imprimir 0,1,2,3,4 (no es este el concepto de cierres?).

En su lugar, imprime 5,5,5,5,5.

Probé Rhino y Firefox.

¿Podría alguien explicarme este comportamiento? Thx por adelantado.

Respuesta

56

fija la respuesta de Jon mediante la adición de 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 termine el ciclo, 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 los 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.

6

Sí, los cierres funcionan aquí. Cada vez que buclea la función que está creando toma el i. Cada función que cree comparte el 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:This article por el Sr. Skeet explica cierres en cierta profundidad y aborda esta cuestión, en particular, de una manera que es mucho más informativo entonces 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 estrategias de captura: complejidad vs potencia" para obtener una explicación sobre este tema.

+0

Entonces, ¿cuál sería su solución (tengo curiosidad ahora también)? –

+0

La respuesta de Jon tiene la solución. –

+0

Es un buen artículo, pero parece que existen algunas diferencias en cómo se implementan cierres entre C# y Javascript. Esto hace que el artículo no sea tan útil con respecto a la pregunta del OP. –

9

Creo que esto podría ser 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); 
    } 
} 
-1

Esto es lo que debe hacer para lograr el 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> 
+3

¿Cómo es este un ejemplo de cierres ahora? Básicamente, solo almacena funciones en la matriz y luego proporciona explícitamente 'i' a través de los argumentos de la función. –

8

La solución es tener una lambda autoejecutable envolver su empuje 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á como se pretende:

function create() { 
    for (var i = 0; i < 5; i++) (function(i) { 
     closures[i] = function() { 
      alert("i = " + i); 
     }; 
    })(i); 
} 

Otra solución sería la creación de un nuevo cierre que captura el valor correcto de I y cesionarios a otra variable que "quedar atrapados" en el final de lambda:

function create() { 
    for (var i = 0; i < 5; i++) (function() { 
     var x = i; 

     closures.push(function() { 
      alert("i = " + x); 
     }); 
    })(); 
} 
+0

Para hacer que la primera implementación sea más clara y comprensible, ¡podría usar un nombre de parámetro diferente al de la función interna! –

+0

@Chetan Sastry, no estaba exactamente después de eso. Como puede ver, incluso la ubicación de la lambda autoejecutable es extraña. Como si no hubiera ningún problema al principio. –

3

John Resig Learning Advanced JavaScript 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 this example se parece mucho al tuyo.

Aquí está el ejemplo rota:

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); 
1

Sólo definiendo una función interna, o asignarlo a una 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 sea saliendo de (en ese punto esas variables externas podrían ser basura, así que será mejor que tomemos una copia).

Es por esto que funciona otra función alrededor de la función interna: el intermediario se ejecuta y sale, seleccionando la función más interna para guardar su propia copia de la pila.

Cuestiones relacionadas