Closure 정리
클로저(Closure)
는 함수와 함수가 선언된 어휘적 환경(Lexical Environment)
을 함께 저장하고 있는 객체이다.
네? 🤪
간단하게 말하면 "함수가 외부 함수의 변수에 접근할 수 있는 권한을 가진 상태" 를 뜻한다.
자바스크립트에서는 함수가 생성될 때 해당 함수가 선언된 환경, 즉 Lexical Environment 를 기억한다. 이 때문에 외부 함수 변수에 접근할 수 있다.
function outer() {
const outerVariable = "outer에서 왔어요!";
function inner() {
console.log(outerVariable); // outerVariable 에 접근 가능
}
return inner;
}
const innerFunction = outer(); // outer 함수 호출
innerFunction(); // ✅ 출력값 : "outer에서 왔어요!"
outer
함수가 실행되면서 outerVariable
은 outer
함수의 로컬 변수로 선언된다.inner
함수는 outer
함수의 안에 선언되어 있기 때문에 outerVariable
변수에 접근할 수 있게 된다.outer
함수가 종료되어도 inner
함수는 여전히 outerVariable
에 접근할 수 있다.inner
함수가 outer
함수의 Lexical Envrionment
를 기억하기 때문이다!: 클로저 사용으로 변수에 직접 접근하지 못하게 하고(은닉화), 특정 메서드를 통해서만 접근하여 조작할 수 있도록 한다.
예시 1
function makeCounter() {
let num = 0; // 은닉화
// -> ❌ 갑자기 99로 증가하는 등 console.log의 출력값 변경 불가
return function () {
return num++; // 내부함수가 외부함수를 참조 (num 값)
};
}
let counter = makeCounter();
console.log(counter()); // 0
console.log(counter()); // 1
console.log(counter()); // 2
예시 2
function createCounter() {
let count = 0; // 외부에서 직접 접근 불가능
return {
increment() {
count++;
console.log(count);
},
decrement() {
count--;
console.log(count);
},
};
}
const counter2 = createCounter();
counter2.increment(); // 1
counter2.increment(); // 2
counter2.decrement(); // 1
: 이벤트 핸들러 같이 "비동기 작업"에서도 유용하게 처리할 수 있다.
function bindEvent(element, message) {
element.addEventListener("click", () => {
console.log(message); // message 기억
});
}
const button = document.querySelector("button");
bindEvent(button, "버튼 클릭!");
: 반복문에서 변수의 스코프 문제를 해결하기 위해 클로저를 사용할 수 있다.
function createFunctions() {
const examples = [];
for (let i = 0; i < 3; i++) {
examples.push(() => {
console.log(i);
});
}
return examples
}
const examFunc = createFunctions();
examFunc[0]() // 0
examFunc[1]() // 1
examFunc[2]() // 2
클로저가 계속 메모리를 참조한다면 이게 과연 좋은걸까?
부트캠프 자바스크립트 강의를 들었을 때 메모리 관련해서 공부하면서 "메모리 공간을 계속 차지하는 것은 비용 낭비이고 좋지 않다" 고 배운 기억이 문득 나버렸다..! 그래서 조금 더 찾아보니 역시나 주의점으로 메모리 누수 이야기가 있었다.
: 프로그램에서 더 이상 사용하지 않는 메모리를 해제하지 못해 메모리가 계속 점유, 사용되는 상황을 뜻한다.
자바스크립트에서는 가비지 컬렉터(Garbage Collector = GC)
가 자동으로 쓰지 않는 메모리를 정리하는데, 이렇게 클로저처럼 계속 참조하여 유지되면 가비지 컬렉터는 메모리를 정리할 수 없어 누수가 발생하게 되는 것이다.
클로저는 함수와 함께 환경을 메모리에 저장하는데, 필요 없는 참조를 계속해서 유지해버리면 메모리 누수가 발생하여 비용 리스크가 발생할 수 있다.
관련 예시 코드
function outer() {
const bigData = new Array(1000000).fill("data");
return function inner() {
console.log(bigData[0]);
};
}
const leak = outer(); // bigData가 클로저로 인해 계속 메모리에 남음
outer
함수 실행 -> largeData
라는 큰 배열이 생성inner
함수 반환 -> 클로저가 largeData
를 참조inner
함수는 여전히 largeData
를 참조하고 있기 때문에, 가비지 컬렉터가 largeData
를 해제하지 못한다.그러면 가비지컬렉터처럼 이 누수를 막아줄 수 있는 방법은 없을까?
가비지컬렉터가 참조하고 있다고 판단해서 삭제하지 못한다면, 우리가 직접 작성해줄 수 없을까?!
null
을 할당하여 참조를 제거하기function addEventListenerWithClosure() {
const largeObject = new Array(1000000).fill("data");
const handler = function () {
console.log(largeObject[0]);
};
const btn = document.getElementById("btn");
btn.addEventListener("click", handler);
// 🗑️ 필요하지 않으면 리스너 제거
btn.removeEventListener("click", handler);
}
addEventListenerWithClosure();
참고자료