함수가 선언될 당시의
스코프(환경)를 기억하고, 함수가 실행될 때에도 해당 스코프에 접근할 수 있는 개념
즉, 내부 함수가 외부 함수의 변수를 참조할 수 있도록 유지되는 현상이다.
'클로저'는 함수가 실행 콘텍스트 스택에서 제거가 되더라도, 렉시컬 환경과 함께 함수가 선언될 당시의 환경을 기억했다가 나중에 호출되었을때 원래의 환경에 따라 수행되는 함수를 의미한다.
const x = 1;
function outer() {
const x = 10;
const inner = function () {
console.log(x);
};
return inner;
}
const getx = outer(); // 1️⃣
getx(); // 10 // 2️⃣
outer함수를 호출하면(1️⃣) outer함수는 중첩함수 inner을 반환하고 생명주기를 마감한다. (실행컨텍스트에서 제거). 이때 outer함수의 지역변수 x와 변수 값 10을 저장하고 있던 outer함수의 실행컨텍스트가 제거되었으므로 outer함수의 지역변수 x 또한 생명주기를 마감한다.
따라서, x변수에 접근할 수 있는 방법은 없어보인다.
하지만 위 코드의 실행결과(2️⃣)는 outer함수의 지역변수인 10이다. 이미 생명주기가 종료되어 실행컨텍스트 스택에서 제거된 outer함수의 지역변수가 다시 부활이라도 한듯이 동작하고있다. 중첩함수 inner가 이미 생명주기를 마감한 outer함수 속 내부함수의 지역변수 x를 참조할 수 있다면 이때의 inner함수를 클로저라고 한다.
이처럼 외부 함수보다 중첩함수가 더 오래 유지되는 경우 중첩함수는 이미 생명주기가 종료한 외부 함수의 변수를 참조할 수 있는데 이런 중첩함수를 '클로저' 라고 한다.
클로저를 사용하면 전역변수 사용을 줄이고, 비슷한 코드의 재사용률을 높여 아래와 같은 이득을 볼 수 있다. 또한 이러한 특성으로, 객체지향 프로그래밍과 밀접한 관계에 있다.
외부에서 직접 접근할 수 없는 변수를 만들 수 있음
변수를 보호하고 특정 함수에서만 접근 가능하도록 설계할 수 있음
function createCounter() {
let count = 0; // private 변수
return {
increment: function () {
count++;
console.log(count);
},
decrement: function () {
count--;
console.log(count);
},
getCount: function () {
return count;
},
};
}
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
console.log(counter.getCount()); // 2
console.log(counter.count); // ❌ undefined (외부에서 접근 불가)
함수가 종료된 후에도 변수를 유지할 수 있어 상태를 관리할 때 유용함
function scoreTracker() {
let score = 0;
return function () {
score += 10;
console.log(`Current Score: ${score}`);
};
}
const updateScore = scoreTracker();
updateScore(); // 10
updateScore(); // 20
updateScore(); // 30
전역 네임스페이스 오염을 방지하고, 재사용 가능한 모듈을 만들 수 있음
const Module = (function () {
let privateVar = "secret";
return {
getSecret: function () {
return privateVar;
},
setSecret: function (newSecret) {
privateVar = newSecret;
},
};
})();
console.log(Module.getSecret()); // "secret"
Module.setSecret("new secret");
console.log(Module.getSecret()); // "new secret"
클로저를 사용하면 장점이 많지만, 메모리 누수와 같은 단점도 존재한다.
클로저는 참조하는 변수들을 GC(Garbage Collector)가 자동으로 제거하지 않는다.
따라서, 필요 없는 데이터가 계속 유지되면 메모리 누수가 발생할 수 있다.
function createBigClosure() {
let hugeArray = new Array(1000000).fill("data");
return function () {
console.log(hugeArray[0]); // 외부 변수를 계속 참조 → 메모리 점유 지속
};
}
const bigFunc = createBigClosure();
bigFunc(); // "data"
// bigFunc를 더 이상 사용하지 않으면 메모리를 해제해야 함!
따라서 클로저가 더 이상 필요하지 않을 경우, 아래와 같이 변수를 null로 할당하여 참조를 끊어줄 필요가 있다.
let bigFunc = createBigClosure();
bigFunc(); // "data"
bigFunc = null; // GC가 메모리 해제할 수 있도록 참조 해제