Armáda funkcí
Následující kód vytvoří pole střelci.
Každá funkce má vypsat své číslo. Ale něco je špatně…
function vytvořArmádu() {
let střelci = [];
let i = 0;
while (i < 10) {
let střelec = function() { // vytvoříme funkci střelec,
alert( i ); // která by měla zobrazit své číslo
};
střelci.push(střelec); // a přidáme ji do pole
i++;
}
// ...a vrátíme pole střelci
return střelci;
}
let armáda = vytvořArmádu();
// všichni střelci zobrazí 10 místo svých čísel 0, 1, 2, 3...
armáda[0](); // 10 od střelce číslo 0
armáda[1](); // 10 od střelce číslo 1
armáda[2](); // 10 ...a tak dále.
Proč všichni střelci zobrazují stejnou hodnotu?
Opravte kód, aby fungoval tak, jak je zamýšleno.
Prozkoumejme, co přesně se děje uvnitř funkce vytvořArmádu, a řešení bude zřejmé.
-
Vytvoří se prázdné pole
střelci:let střelci = []; -
V cyklu se naplní funkcemi pomocí
střelci.push(function).Každý prvek je funkce, takže výsledné pole vypadá takto:
střelci = [ 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); } ]; -
Toto pole funkce vrátí.
Pak později volání kteréhokoli jeho prvku, např.
armáda[5](), načte z pole prvekarmáda[5](což je funkce) a zavolá jej.Proč nyní všechny tyto funkce zobrazí stejnou hodnotu,
10?Je to proto, že uvnitř funkcí
střelecneexistuje žádná lokální proměnnái. Když je taková funkce volána, převezmeiz vnějšího lexikálního prostředí.Jaká pak bude hodnota proměnné
i?Když se podíváme na zdrojový kód:
function vytvořArmádu() { ... let i = 0; while (i < 10) { let střelec = function() { // funkce střelec alert( i ); // by měla zobrazit své číslo }; střelci.push(střelec); // přidá funkci do pole i++; } ... }Vidíme, že všechny funkce
střelecjsou vytvořeny v lexikálním prostředí funkcevytvořArmádu(). Když je však volánaarmáda[5](), funkcevytvořArmádujiž ukončila svou práci a poslední hodnotaije10(whilese zastaví nai=10).Výsledkem je, že všechny funkce
střelecpřevezmou z vnějšího lexikálního prostředí stejnou hodnotu a tou bude poslední hodnota,i=10.Jak vidíte výše, při každé iteraci bloku
while {...}bude vytvořeno nové lexikální prostředí. Abychom to opravili, můžeme zkopírovat hodnotuido proměnné uvnitř blokuwhile {...}třeba takto:function vytvořArmádu() { let střelci = []; let i = 0; while (i < 10) { let j = i; let střelec = function() { // funkce střelec alert( j ); // by měla zobrazit své číslo }; střelci.push(střelec); i++; } return střelci; } let armáda = vytvořArmádu(); // Nyní kód funguje správně armáda[0](); // 0 armáda[5](); // 5Zde
let j = ideklaruje „iteračně lokální“ proměnnouja zkopíruje do níi. Primitivy se kopírují „hodnotou“, takže ve skutečnosti získáme nezávislou kopiii, která patří do aktuální iterace cyklu.Střelci budou fungovat správně, protože hodnota
inyní existuje trochu blíže. Není v lexikálním prostředí funkcevytvořArmádu(), ale v lexikálním prostředí, které odpovídá aktuální iteraci cyklu:Tomuto problému se lze vyhnout i tak, že na začátku použijeme
for, třeba takto:function vytvořArmádu() { let střelci = []; for(let i = 0; i < 10; i++) { let střelec = function() { // funkce střelec alert( i ); // by měla zobrazit své číslo }; střelci.push(střelec); } return střelci; } let armáda = vytvořArmádu(); armáda[0](); // 0 armáda[5](); // 5To je v zásadě totéž, protože
forpři každé své iteraci vygeneruje nové lexikální prostředí se svou vlastní proměnnoui. Takžestřelecgenerovaný v každé iteraci odkazuje na své vlastníipřímo z této iterace.
Když jste vložili tolik námahy do přečtení tohoto řešení a konečný recept je tak jednoduchý – prostě použijeme for, můžete se divit – mělo to cenu?
Inu, kdybyste na tuto otázku dokázali snadno odpovědět, nečetli byste řešení. Snad vám tedy tato úloha pomohla trochu lépe všemu porozumět.
Kromě toho zajisté existují případy, kdy člověk dává přednost while před for, i jiné scénáře, v nichž takové problémy opravdu nastanou.