문제상황
run = (timeout: number) => new Promise<void>((resolve) => {
let direction = 'r';
const todos = [];
for (let i = 0; i < 15; i += 1) {
if (i === 6) direction = 'l';
if (i === 10) direction = 'r';
todos.push(() => { this.dragonBGmove(`${direction + 4}`, timeout); });
todos.push(() => { this.dragonBGmove(`${direction + 3}`, timeout); });
}
resolve();
});
7,8번째 줄에서 다음과 같은 에러가 뜬다.
Function declared in a loop contains unsafe references to variable(s) 'direction'.
왜 이런 에러메세지가 나왔는지 짐작은간다. todos를 루프 돌리면서 안에있는 함수를 호출할때는 이미 direction이 내가 의도한 값이 아니기 때문이다.
예를들어서,
class TEST {
run = () => {
let a = 10;
const todos = [];
for (let i = 0; i < 4; i += 1) {
if (i%2 === 0) a += 1;
todos.push(function(){ this.move(a); }.bind(this));
todos.push(() => { this.move(a) })
}
todos.forEach((fn) => {
fn();
});
}
move = (variable) => {
console.log(variable);
}
}
var obj = new TEST();
obj.run();
// 12
// 12
// 12
// 12
// 12
// 12
// 12
// 12
실행해보면 12만 계속 나온다. 당연하다! 실행시점에서 a
를 찾아 찾아 위로 올라갔을때(scope chain) 이미 a
값은 12가 되어있기 때문이다. 그래서 todos
에 push하는 시점의 a
값을 잡아놔야한다.
조사
Bind를 쓰라!
class TEST {
run = () => {
let a = 10;
const todos = [];
for (let i = 0; i < 4; i += 1) {
if (i%2 === 0) a += 1;
todos.push(function(val){ this.move(val); }.bind(this, a));
todos.push(((val) => { this.move(val) }).bind(this, a));
}
todos.forEach((fn) => {
fn();
});
}
move = (variable) => {
console.log(variable);
}
}
var obj = new TEST();
obj.run();
// 11
// 11
// 11
// 11
// 12
// 12
// 12
// 12
해결책 정리
bind를 사용해서 아래와 같이 수정했다.
run = (timeout: number) => new Promise<void>((resolve) => {
let direction = 'r';
const todos = [];
for (let i = 0; i < 15; i += 1) {
if (i === 6) direction = 'l';
if (i === 10) direction = 'r';
todos.push(this.dragonBGmove.bind(this, direction + 4, timeout));
todos.push(this.dragonBGmove.bind(this, direction + 3, timeout));
}
resolve();
});