Javascript 공부를 하면 항상 나오는 개념이 클로저(Closure)다. 아는 것 같지만 모르는게 더 많은 거 같은 신비한 클로저에 대해 정리해보고자 한다.

클로저의 의미

클로저는 Javascript만 사용하는 개념은 아니다. 함수를 일급 함수로 다루는 언어 혹은 런타임에 함수를 생성하는 언어에서 발생하는 현상을 이용한 개념이다.

namespace DisplayConstants
{
    class Program
    {
				private int num;
        static void Main(string[] args)
        {
            double num1 = 10.9;
            double num2 = 52.16;
            Console.WriteLine("일반 덧셈 결과: ", (num1 + num2));

            double num3 = 10.9;
            int num4 = (int)num3;
            Console.WriteLine(num4);
        }
    }
}

위 코드는 C#코드다. C#은 정적으로 메소드를 분석하여 컴파일 타임에 메모리 공간을 잡아둔다고 한다. 즉, 컴파일 단계에서 메소드가 접근할 수 있는 변수들을 계산해둔다. 하지만 Javscript는 다르다.

const outer = function() {
    let a = 1;
    const inner = function() {
				return ++a;
    }
    return inner;
}

const outer2 = outer();
console.log(outer2()); // 2
console.log(outer2()); // 3

Javascript에서 함수는 함수를 값으로 취급하는 일급함수이다. 또한, 함수를 런타임에 생성도 가능하다. 즉, 정적으로 분석할 수 없기 때문에 실행 컨텍스트 방식을 이용한다.

위 코드에서 스코프는 전역 스코프, outer 함수 내 스코프, inner 함수 내 스코프가 있고, inner 함수 내에 있는 변수 a는 상위 스코프(outer)에 있는 a 변수를 참조하고 있다. outer 함수는 inner 함수를 외부로 반환하여 outer2 에 할당하여 호출하고 있다.

outer 스코프 내에 있는 변수를 참조하는 inner 함수를 외부로 노출하면서 outer 함수 내에 a에 대한 GC에 의해 수거가 불가능해진 것이다.(a 참조 카운트가 생김) 이 상황이 클로저다.

위 상황을 좀 더 일반적으로 정의하자면, 외부 스코프에 존재하는 자유 변수(a)가 내부 스코프에 의해 제거되지 않고, 갇혀진(closure)현상을 일컫는 말이다.

클로저와 메모리

클로저가 발생하면 메모리가 GC에 의해 해제되지 않기 때문에 관리 해줘야 한다. 당연히 GC가 되지 않기에 GC에 의해 수거될 수 있도록 참조 카운트를 0으로 만들어야 한다. 이 때 사용할 수 있는 방법이 null을 할당하는 방법이다.

const outer = function() {
    let a = 1;
    const inner = function() {
				return ++a;
    }
    return inner;
}

const outer2 = outer();
console.log(outer2()); // 2
console.log(outer2()); // 3
outer2 = null;  //  GC에 의해 수거됨

클로저의 활용

“외부 스코프의 변수를 잡아두고, 이를 외부에 노출한다.”라는 점이 클로저의 특징이다. 이를 조금 더 생각해보면 외부에 대한 내부의 노출을 어디까지 허용할 것인가를 의미하기도 한다. 즉, 접근 제어의 특징으로도 활용할 수 있다. 이 두가지 특성을 이용한 활용 방법에 대해 알아보자.

콜백 함수 내부에서 외부 데이터를 사용하고자 할 때

const fruits = ["🍎","🍊","🍓","🍑"];

const ulEl = document.createElement("ul");
// 무명함수 콜백
fruits.forEach(fruit => {
    const liEl = document.createElement('li')
    liEl.innerText=fruit
    liEl.addEventListener('click', () => {
        alert(`${fruit} click`);
    })
    ulEl.appendChild(liEl)
})

document.body.appendChild(ulEl)

ul 태그를 만들고 li 태그를 생성하여 과일들을 나열하고 각 li 태그에 eventListener를 달아서 alert창을 띄우도록 하는 코드다.

위 코드에서 li 태그를 선택하면 작성한대로 alert창이 뜬다. 하지만, 과일창을 alert창 기능을 따로 분리하고 싶어서 따로 빼서 콜백으로 전달한다면 문제가 발생한다.