클로저는 함수가 선언된 환경의(Lexical) 스코프를 기억하여 스코프 밖에서 함수를 실행 할 때에도 접근할 수 있는 문법이다.
Lexical Scope - 자바스크립트 엔진은 함수를 어디서 호출했는지가 아니라 함수를 어디서 선언 했는지에 따라 상위 스코프를 결정한다. 이를 렉시컬 스코프(정적 스코프)라 한다.
function addMaker(a) {
function(b) {
return a + b;
};
}
const add5 = addMaker(5);
const add20 = addMaker(20);
add5(10); // a = 5, b = 10, a + b = 15
add20(5); // a = 20, b = 5, a + b = 25
add5와 add20은 a를 기억하는 클로저이다.
function counter(fn) {
let count = 0;
return function() {
count = fn(count);
return count;
};
}
// 증가 함수
function increase(n) {
return ++n;
}
// 감소 함수
function decrease(n) {
return --n;
}
// decrease 함수와 별개의 독립된 환경을 가진다.
const 증가 = counter(increase);
console.log(증가()); // 1
console.log(증가()); // 2
// increase 함수와 별개의 독립된 환경을 가진다.
const 감소 = counter(decrease);
console.log(감소()); // -1
console.log(감소()); // -2
증가, 감소 함수는 서로 다른 독립된 렉시컬 환경을 갖게 된다.
스코프는 쉽게 말해 변수나 함수의 유효 범위라고 부른다.
유효 범위 밖에서는 접근할 수 없다.
const, let의 범위는 block 스코프이다.
var의 범위는 function 스코프이다.
function time() {
for(var i = 1; i <= 5 ; i++) {
setTimeout(()=> {
console.log(i);
}, i * 100);
}
}
time();
위 time 함수를 호출 했을때 어떤 값이 나올지 생각해보자
1,2,3,4,5가 순서대로 나올것이라 생각 했지만, 결과는 6이 5번 호출되고 있다. 그 이유는 var 변수의 스코프 범위가 함수이기 때문이다.
// var => let 변수 변경
function time() {
for(let i = 1 ; i <= 5 ; i++) {
setTimeout(()=>{
console.log(i)
}, i * 100);
};
}
time();
// 즉시실행 함수를 설정하여 스코프 범위 변경
function time() {
for(let i = 1 ; i <= 5 ; i++) {
(function(i) {
setTimeout(()=>{
console.log(i)
}, i * 100);
})(i);
};
}
time();
이를 해결 하기 위해서는 var 변수를 let 변수로 변경하여 스코프 범위를 블록 스코프로 변경해주거나 즉시 실행 함수를 사용하여 매개변수로 i를 전달하여 해당 함수 안에 값을 기억하게 만들어줘야 한다.