Ejército de funciones
El siguiente código crea una serie de shooters.
Cada función está destinada a generar su número. Pero algo anda mal …
function makeArmy() {
let shooters = [];
let i = 0;
while (i < 10) {
let shooter = function() { // crea la función shooter
alert( i ); // debería mostrar su número
};
shooters.push(shooter); // y agregarlo al array
i++;
}
// ...y devolver el array de tiradores
return shooters;
}
let army = makeArmy();
// ... todos los tiradores muestran 10 en lugar de sus 0, 1, 2, 3 ...
army[0](); // 10 del tirador número 0
army[1](); // 10 del tirador número 1
army[2](); // 10 ...y así sucesivamente.
¿Por qué todos los tiradores muestran el mismo valor?
Arregle el código para que funcionen según lo previsto.
Examinemos lo que sucede dentro de makeArmy, y la solución será obvia.
-
Esta crea un array vacío de tiradores,
shooters:let shooters = []; -
Lo llena en el bucle a través de
shooters.push(function...).Cada elemento es una función, por lo que el array resultante se ve así:
shooters = [ function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); } ]; -
El array se devuelve desde la función.
Más tarde la llamada a cualquier miembro, por ejemplo
army[5](), obtendrá el elementoarmy[5]del array (será una función) y lo llamará.Ahora, ¿por qué todas esas funciones muestran el mismo valor,
10?Esto se debe a que no hay una variable local
identro de las funcionesshooter. Cuando se llama a tal función, tomaide su entorno léxico externo.Entonces ¿cuál será el valor de
i?Si miramos la fuente:
function makeArmy() { ... let i = 0; while (i < 10) { let shooter = function() { // shooter function alert( i ); // debería mostrar su número }; shooters.push(shooter); // agrega la función al array i++; } ... }Podemos ver que todas las funciones
shooterestán creadas en el ambiente léxico asociado a la ejecución demakeArmy(). Pero cuando se llama aarmy[5](),makeArmyya ha terminado su trabajo, y el valor final deies10(whilefinaliza eni=10).Como resultado, todas las funciones
shooterobtienen el mismo valor del mismo entorno léxico externo, que es el último valori=10.Como puedes ver arriba, con cada iteración del bloque
while {...}un nuevo ambiente léxico es creado. Entonces, para corregir el problema podemos copiar el valor deien una variable dentro del bloquewhile {...}como aquí:function makeArmy() { let shooters = []; let i = 0; while (i < 10) { let j = i; let shooter = function() { // shooter function alert( j ); // debería mostrar su número }; shooters.push(shooter); i++; } return shooters; } let army = makeArmy(); // Ahora el código funciona correctamente army[0](); // 0 army[5](); // 5Aquí
let j = ideclara una variable de iteración localjy copiaien ella. Las primitivas son copiadas por valor, así que realmente obtenemos una copia independiente dei, perteneciente a la iteración del bucle actual.Los shooters funcionan correctamente, porque el valor de
iahora vive más cerca. No en el ambiente léxico demakeArmy()sino en el que corresponde a la iteración del bucle actual:Tal problema habría sido evitado si hubiéramos usado
fordesde el principio:function makeArmy() { let shooters = []; for(let i = 0; i < 10; i++) { let shooter = function() { // shooter function alert( i ); // debería mostrar su número }; shooters.push(shooter); } return shooters; } let army = makeArmy(); army[0](); // 0 army[5](); // 5Esto es esencialmente lo mismo, ya que cada iteración de
forgenera un nuevo ambiente léxico con su propia variablei. Así elshootergenerado en cada iteración hace referencia a su propioi, de esa misma iteración.
Ahora, como has puesto mucho esfuerzo leyendo esto, y la receta final es tan simple: simplemente usa for, puede que te preguntes: ¿valió la pena?
Bien, si pudiste resolver el problema fácilmente probablemente no habrías necesitado leer la solución, así que esperamos que esta tarea te haya ayudado a entender las cosas mejor.
Además, efectivamente hay casos donde uno prefiere while a for, y otros escenarios donde tales problemas son reales.