함수를 사용해 군대 만들기
아래 코드는 shooters
가 요소인 배열을 만들어줍니다.
모든 함수는 몇 번째 shooter인지 출력해줘야 하는데 뭔가 잘못되었습니다.
function makeArmy() {
let shooters = [];
let i = 0;
while (i < 10) {
let shooter = function() { // shooter 함수
alert( i ); // 몇 번째 shooter인지 출력해줘야 함
};
shooters.push(shooter);
i++;
}
return shooters;
}
let army = makeArmy();
army[0](); // 0번째 shooter가 10을 출력함
army[5](); // 5번째 shooter 역시 10을 출력함
// 모든 shooter가 자신의 번호 대신 10을 출력하고 있음
왜 모든 shooter가 동일한 숫자를 출력하는 걸까요? 제대로 된 번호가 출력될 수 있게 코드를 수정해 보세요.
Let’s examine what’s done inside makeArmy
, and the solution will become obvious.
-
It creates an empty array
shooters
:let shooters = [];
-
Fills it in the loop via
shooters.push(function...)
.Every element is a function, so the resulting array looks like this:
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); } ];
-
The array is returned from the function.
Then, later, the call to army[5]()
will get the element army[5]
from the array (it will be a function) and call it.
Now why all such functions show the same?
That’s because there’s no local variable i
inside shooter
functions. When such a function is called, it takes i
from its outer lexical environment.
What will be the value of i
?
If we look at the source:
function makeArmy() {
...
let i = 0;
while (i < 10) {
let shooter = function() { // shooter function
alert( i ); // should show its number
};
...
}
...
}
…We can see that it lives in the lexical environment associated with the current makeArmy()
run. But when army[5]()
is called, makeArmy
has already finished its job, and i
has the last value: 10
(the end of while
).
As a result, all shooter
functions get from the outer lexical envrironment the same, last value i=10
.
We can fix it by moving the variable definition into the loop:
function makeArmy() {
let shooters = [];
for(let i = 0; i < 10; i++) {
let shooter = function() { // shooter function
alert( i ); // should show its number
};
shooters.push(shooter);
}
return shooters;
}
let army = makeArmy();
army[0](); // 0
army[5](); // 5
Now it works correctly, because every time the code block in for (let i=0...) {...}
is executed, a new Lexical Environment is created for it, with the corresponding variable i
.
So, the value of i
now lives a little bit closer. Not in makeArmy()
Lexical Environment, but in the Lexical Environment that corresponds the current loop iteration. That’s why now it works.
Here we rewrote while
into for
.
Another trick could be possible, let’s see it for better understanding of the subject:
function makeArmy() {
let shooters = [];
let i = 0;
while (i < 10) {
let j = i;
let shooter = function() { // shooter function
alert( j ); // should show its number
};
shooters.push(shooter);
i++;
}
return shooters;
}
let army = makeArmy();
army[0](); // 0
army[5](); // 5
The while
loop, just like for
, makes a new Lexical Environment for each run. So here we make sure that it gets the right value for a shooter
.
We copy let j = i
. This makes a loop body local j
and copies the value of i
to it. Primitives are copied “by value”, so we actually get a complete independent copy of i
, belonging to the current loop iteration.