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
i
dentro de las funcionesshooter
. Cuando se llama a tal función, tomai
de 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
shooter
están creadas en el ambiente léxico asociado a la ejecución demakeArmy()
. Pero cuando se llama aarmy[5]()
,makeArmy
ya ha terminado su trabajo, y el valor final dei
es10
(while
finaliza eni=10
).Como resultado, todas las funciones
shooter
obtienen 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 dei
en 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](); // 5
Aquí
let j = i
declara una variable de iteración localj
y copiai
en 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
i
ahora 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
for
desde 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](); // 5
Esto es esencialmente lo mismo, ya que cada iteración de
for
genera un nuevo ambiente léxico con su propia variablei
. Así elshooter
generado 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.