클로저(Closure)란?
클로저는 자바스크립트의 강력한 기능 중 하나입니다. 자바스크립트에서는 함수 안에 또 다른 함수를 정의하는 중첩 함수(nested function)가 허용되며, 내부 함수는 외부(부모) 함수의 변수에 접근할 수 있습니다. 이처럼 내부 함수가 외부 함수의 변수에 접근할 수 있는 현상을 클로저라고 부릅니다.
그런데 여기서 문제가 발생합니다. 선언된 변수가 내부 중첩 함수에서 실제로 참조되지 않더라도, 클로저 구조로 인해 해당 변수는 메모리에 계속 남아 있게 됩니다. 이렇게 불필요하게 메모리에 상주하는 변수는 가비지 컬렉션의 대상이 되지 못하고, 결국 메모리 누수(memory leak)의 원인이 됩니다.
클로저로 인한 메모리 누수의 동작 원리
다음 예제에서 'childFunction'은 외부 함수인 'parentFunction' 내부에 정의된 내부 함수입니다. 'parentFunction'을 매개변수 "outer val"과 함께 호출하면 외부 변수 a에는 "outer val"이라는 값이 할당됩니다. 그리고 이 함수는 내부 함수 'childFunction'에 대한 참조(포인터)를 반환하며, 이 반환값은 변수 'val'에 저장됩니다.
흥미로운 점은, 외부 함수가 이미 실행을 마치고 반환되었음에도 불구하고 외부 함수의 지역 변수 a가 여전히 존재한다는 것입니다. 자바스크립트에서 parentFunction이 호출되는 순간, 속성 'a'를 가진 스코프 객체(scope object)가 생성됩니다. 이 속성에는 arg1, 즉 "outer val"이라는 값이 담깁니다. 마찬가지로 parentFunction이 반환될 때는 내부 함수(childFunction)를 반환하며, 이 함수는 변수 val에 저장됩니다.
핵심은, 내부 함수가 외부 함수의 변수에 대한 참조를 계속 유지하고 있다는 점입니다. 따라서 속성 'a'를 포함하는 스코프 객체는 가비지 컬렉션(garbage collection) 대상에서 제외되고, 메모리에서 해제되지 않은 채 남아 있게 됩니다. 이것이 바로 클로저로 인해 메모리 누수가 발생하는 근본적인 이유입니다.
예제 코드
<html>
<body>
<script>
window.onload= function parentFunction(arg1) {
var a = arg1;
return function childFunction (arg2)
{
alert( a +" "+ arg2);
};
};
var val = parentFunction("outer val");
val("inner val");
</script>
</body>
</html>메모리 누수 피하는 방법
함수를 하나 더 추가하기
함수를 하나 더 추가하면 내부 함수가 두 개가 됩니다. 내부 함수가 두 개 존재하게 되면, 어떤 함수도 외부 함수의 변수를 직접 참조하지 않게 되어 결과적으로 클로저 자체가 형성되지 않습니다.
클로저가 생성되지 않으면 외부 함수의 스코프 객체가 가비지 컬렉션에 의해 정상적으로 해제될 수 있으므로, 메모리 누수가 발생할 가능성이 크게 줄어듭니다.
예제 코드
<html>
<body>
<script>
window.onload=function parentFunction(){
var Obj1 = function childFunction1()
{
document.write("the leak is avoided");
};
(function childFunction2(){
var obj2 = document.getElementById("closure");
obj2.onclick=Obj1
})();
};
</script>
<input type = "button" id="closure" value ="Click Here">
</body>
</html>위 코드를 실행하면 다음과 같은 버튼이 화면에 표시됩니다.

버튼을 클릭하면 아래와 같은 출력 결과를 확인할 수 있습니다.
실행 결과
the leak is avoided