클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경의 조합입니다.
쉽게 말해 함수 내부에서 선언한 변수와 함수들은 해당 함수 외부에서 접근할 수 없습니다.
하지만 함수 내부에서 선언한 변수를 참조하는 함수가 반환될 경우, 그 함수와 변수들을 함께 기억하고 있는 것을 클로저라고 합니다.
(함수를 만들었는데, 안의 변수들을 접근할 수 없다 . 대신 함수를 반환할 때, 그 함수에서는 변수들을 기억하고 있는다.)
예시 코드를 통해 개념을 보겠습니다.
function outerFunc(){
const outerVar = 'OUTER';
function innerFunc(){
const innerVar = "INNER"
console.log(outerVar + innerVar);
}
return innerFunc;
}
const inner = outerFunc();
inner() // OUTER INNER
outerFunc
는 innerFunc 함수를 반환합니다.
이 때 outerFunc 함수가 실행될 때 생성된 렉시컬 환경에서는 outerVar
변수를 참조합니다.
따라서 outerFuc 함수가 실행되고 종료될 때도 outerVar 변수의 값은 유지됩니다.
원래라면 함수가 종료되면 변수가 없어진다고 볼 수 있는데, innerFunc이 inner 로 저장되어 있기 때문에, 계속해서 outerVar를 참조할 수 있습니다.
const inner = outerFunc();
위 함수가 먼저 실행되기 떄문에, 함수가 이미 종료된 이후 입니다.
그러나 클로저 덕분에, inner() 를 통해 innerFunc 함수 내부에서는 outerVar 변수를 참조할 수 있습니다.
다른 예시를 들어보겠습니다.
function createCounter() {
let count = 0;
return function() {
count++
return count
};
}
counter = createCounter()
a = counter() // a => 1
b = counter() // b => 2
c = counter() // c => 3
위 예시 코드에서는 createCounter 함수가 호출될 때마다 count 가 초기화되면서, 익명 함수가 반환됩니다.
클로저는 이벤트 핸들러에서 많이 사용됩니다.
이벤트 핸들러 함수는 외부 스코프에 정의된 변수에 접근해야 하는데, 이 때 클로저를 사용하여 해당 변수에 접근할 수 있습니다.
function createCounter() {
let count = 0;
document.getElementById("button").addEventListener("click", function() {
count++;
console.log(count);
});
}
createCounter();
비동기 콜백 함수에서도 클로저가 사용됩니다. 비동기 함수 내부에서 참조하는 변수가 비동기 함수가 반환된 이후에도 유지되어야 하는 경우에 클로저를 사용합니다.
function createCounter() {
let count = 0;
setTimeout(function() {
count++;
console.log(count);
}, 1000);
}
createCounter();
function createCounter() {
let count = 0;
return {
getCount: function() {
return count;
},
increment: function() {
count++;
}
};
}
const counter = createCounter();
console.log(counter.getCount()); // 0
counter.increment();
console.log(counter.getCount()); // 1
클로저는 함수 내부에서 사용되는 변수를 함수가 반환된 이후에도 계속해서 사용할 수 있도록 만들어주는 것입니다. 이를 가능하게 하는 것은 함수가 선언됐을 때의 환경을 기억하고 있기 때문입니다. 따라서, 클로저는 함수를 둘러싼 스코프의 변수를 "캡처"하여, 해당 변수를 함수 외부에서도 계속해서 사용할 수 있도록 해줍니다.