mdn - ¿Javascript infame problema de bucle?
mdn for loop (5)
Esta pregunta ya tiene una respuesta aquí:
Tengo el siguiente fragmento de código.
function addLinks () {
for (var i=0, link; i<5; i++) {
link = document.createElement("a");
link.innerHTML = "Link " + i;
link.onclick = function () {
alert(i);
};
document.body.appendChild(link);
}
}
El código anterior es para generar 5 enlaces y vincular cada enlace con un evento de alerta para mostrar la identificación del enlace actual. Pero no funciona. Cuando haces clic en los enlaces generados, todos dicen "enlace 5".
Pero el siguiente fragmento de código funciona como nuestra expectativa.
function addLinks () {
for (var i=0, link; i<5; i++) {
link = document.createElement("a");
link.innerHTML = "Link " + i;
link.onclick = function (num) {
return function () {
alert(num);
};
}(i);
document.body.appendChild(link);
}
}
Los 2 fragmentos de código anteriores se citan desde here . Según la explicación del autor, parece que el cierre hace la magia.
Pero cómo funciona y cómo el cierre lo hace funcionar están más allá de mi comprensión. ¿Por qué el primero no funciona mientras el segundo funciona? ¿Alguien puede dar una explicación detallada sobre la magia?
Gracias.
Básicamente, en el primer ejemplo, estás vinculando la i
dentro del controlador onclick
directamente a la i
fuera del controlador onclick
. Entonces, cuando el i
fuera del controlador onclick
cambia, el i
dentro del controlador onclick
también cambia.
En el segundo ejemplo, en lugar de vincularlo al num
en el controlador onclick
, lo está pasando a una función, que luego lo vincula al num
en el controlador onclick
. Cuando lo pasa a la función, el valor de i
se copia, no se limita a num
. Así que cuando i
cambio, num
permanece igual. La copia se produce porque las funciones en JavaScript son "cierres", lo que significa que una vez que se pasa algo a la función, se "cierra" para una modificación externa.
En el primer ejemplo, simplemente vincula esta función al evento onclick:
function() {alert(i);};
Esto significa que en el evento click, js debe alertar sobre el valor de la variable addlink functions i. Su valor será 5 debido al bucle for ().
En el segundo ejemplo, generas una función para vincularse con otra función:
function (num) {
return function () { alert(num); };
}
Esto significa: si se llama con un valor, devuélvame una función que alertará el valor de entrada. Por ejemplo, la function(3)
llamada function(3)
devolverá la function() { alert(3) };
.
Llama a esta función con el valor i en cada iteración, por lo tanto, crea funciones onclick separadas para cada enlace.
El punto es que en el primer ejemplo su función contenía una referencia variable, mientras que en el segundo, con la ayuda de la función externa, sustituyó la referencia con un valor real. Esto se denomina cierre aproximadamente porque usted "encierra" el valor actual de una variable dentro de su función en lugar de mantener una referencia a ella.
Me gusta escribir explicaciones simples para personas gruesas, porque yo soy gorda, así que aquí va ...
Tenemos 5 divs en la página, cada uno con una identificación ... div1, div2, div3, div4, div5
jQuery puede hacer esto ...
for (var i=1; i<=5; i++) {
$("#div" + i).click ( function() { alert ($(this).index()) } )
}
Pero realmente abordando el problema (y aumentando esto lentamente) ...
PASO 1
for (var i=1; i<=5; i++) {
$("#div" + i).click (
// TODO: Write function to handle click event
)
}
PASO 2
for (var i=1; i<=5; i++) {
$("#div" + i).click (
function(num) {
// A functions variable values are set WHEN THE FUNCTION IS CALLED!
// PLEASE UNDERSTAND THIS AND YOU ARE HOME AND DRY (took me 2 years)!
// Now the click event is expecting a function as a handler so return it
return function() { alert (num) }
}(i) // We call the function here, passing in i
)
}
SIMPLE PARA ENTENDER ALTERNATIVA
Si no puedes entenderlo, entonces esto debería ser más fácil de entender y tiene el mismo efecto ...
for (var i=1; i<=5; i++) {
function clickHandler(num) {
$("#div" + i).click (
function() { alert (num) }
)
}
clickHandler(i);
}
Esto debería ser fácil de entender si recuerda que los valores de las variables de una función se establecen cuando se llama a la función (pero esto utiliza el mismo proceso de pensamiento que antes)
Otros han explicado lo que está pasando, aquí hay una solución alternativa.
function addLinks () {
for (var i = 0, link; i < 5; i++) {
link = document.createElement("a");
link.innerHTML = "Link " + i;
with ({ n: i }) {
link.onclick = function() {
alert(n);
};
}
document.body.appendChild(link);
}
}
Básicamente los pobres sirven para atar.
Me cito para una explicación del primer ejemplo:
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 de encuadre se agrega al entorno léxico de la función encerrada.
Una vez que el bucle termina, la variable de nivel de función i tiene el valor 5, y eso es lo que la función interna "ve".
En el segundo ejemplo, para cada paso de iteración, el literal de la función externa se evaluará como un nuevo objeto de función con su propio alcance y número de variable local, cuyo valor se establece en el valor actual de i
. Como nunca se modifica el número, permanecerá constante durante la vida útil del cierre: el siguiente paso de iteración no sobrescribe el valor anterior ya que los objetos de función son independientes.
Tenga en cuenta que este enfoque es bastante ineficiente ya que se deben crear dos nuevos objetos de función para cada enlace. Esto no es necesario, ya que pueden compartirse fácilmente si utiliza el nodo DOM para el almacenamiento de información:
function linkListener() {
alert(this.i);
}
function addLinks () {
for(var i = 0; i < 5; ++i) {
var link = document.createElement(''a'');
link.appendChild(document.createTextNode(''Link '' + i));
link.i = i;
link.onclick = linkListener;
document.body.appendChild(link);
}
}