클로저란 무엇인가 ?
클로저란 함수와 해당 함수가 만들어진 환경을 모두 저장하는 개념이다.
이러한 방식은 함수가 자신이 만들어질 때의 변수들에 접근할 수 있도록 만든다.
함수가 속한 렉시컬 스코프({})를 기억하여 함수가 렉시컬 스코프 밖에서 실행될 때도
그 스코프에 접근할 수 있도록하는 기능인 것이다.
cntFn = () => {
let cnt = 0;
return function () {
cnt++;
console.log(cnt);
};
};
const counter = cntFn();
counter(); // 결과 :1
counter(); // 결과 :2
위의 예시처럼 클로저는 주로 함수 안에 함수를 반환하는 방식으로 만들거나
콜백함수를 의미하거나 함수를 인자로 받는 고차함수(higher-odrer-function)에
사용된다. 클로저를 사용하면 내부 함수가 외부 함수의 변수에 접근할 수 있고
이를 통해 데이터를 보호하거나 유지할 수 있다는 이점이 있다.
클로저의
다음은 반복문 클로저이다.
cnt = () => {
for (var i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i);
}, 3000);
}
};
cnt(); // 결과 : 10 10 10 10 10.... 10
0 1 2 3 4.. 9 이 아닌 10이 10번 출력되었다.
이 경우 setTimeout()이 반복문 내에서 실행돼 콜백함수가 task queue에 쌓이고
call stack으로 돌아와 실행된다.
이 때 콜백함수는 클로저기 때문에 상위 스코프인 cnt에 값을 구하고 cnt는 이미 반복문이 끝난 시점의 10을
연달아 반환한다. 그래서 결론적으로 10만 4번 출력된 것이다.
해결방법
의외로 해결방법은 간단한데, 해결법보다 원리를 아는 것이 중요한 듯 싶다.
클로저란 결국 스코프를 기억해서 그 스코프 내에 접근하는 것인데,
함수 스코프가 아닌 블록 스코프로 대체하면 해결된다.
즉, var 변수 선언은 함수 스코프를 갖지만, let 키워드는
블록 스코프를 갖기 때문에 for문 내에서 새로운 스코프를 형성하고
매 반복마다 새로운 i가 선언돼 값이 초기화 된다.
콜백함수가 상위 스코프에게 값을 요청해도 초기화된 i가 참조되기 때문에
의도된 대로 사용될 수 있는 것이다.
cnt = () => {
for (let i = 0; i < 10; i++) {
// 여기 변화
setTimeout(() => {
console.log(i);
}, 3000);
}
};
cnt(); // 결과 : 0 1 2 3 4 5 ... 9