Computer >> 컴퓨터 >  >> 프로그래밍 >> JavaScript

자바스크립트(JavaScript) 클로저의 실전 활용법 완벽 가이드

클로저(Closure)란 함수와 그 함수가 선언된 당시의 렉시컬 환경(Lexical Environment)이 결합된 것을 의미합니다. 쉽게 말해, 내부 함수가 외부 함수의 변수에 계속 접근할 수 있도록 해주는 자바스크립트의 핵심 개념입니다.

클로저의 기본 동작 원리

function outerFunc() {
    var name = "TutorialsPoint"; // outerFunc가 생성한 지역 변수
    return function innerFunc() {
        // innerFunc()는 내부 함수이자 클로저입니다
        console.log(name); // 부모 함수에서 선언된 변수에 접근
    };
}
let f = outerFunc();
f(); // "TutorialsPoint" 출력

outerFunc()name이라는 지역 변수와 innerFunc()라는 함수를 생성합니다. innerFunc()outerFunc() 내부에서 정의된 내부 함수로, 오직 outerFunc()의 본문 안에서만 유효합니다.

흥미로운 점은 innerFunc()에는 자체적인 지역 변수가 없다는 것입니다. 하지만 내부 함수는 외부 함수의 변수에 접근할 수 있기 때문에, 부모 함수인 outerFunc()에서 선언된 name 변수를 그대로 사용할 수 있습니다.

일반적으로 outerFunc()의 실행이 끝나면 name 변수는 스코프에서 벗어나 소멸해야 합니다. 그럼에도 불구하고 반환되어 f에 할당된 innerFunc는 여전히 name 변수에 접근할 수 있습니다. 이것이 바로 innerFuncouterFunc의 렉시컬 스코프 위에 클로저를 형성했다는 의미입니다.

클로저의 실전 활용 사례

1. 비공개(private) 메서드 흉내 내기

Java와 같은 언어는 메서드를 private으로 선언하여 같은 클래스 내부의 다른 메서드에서만 호출할 수 있도록 제한하는 기능을 제공합니다. 자바스크립트는 이러한 기능을 네이티브하게 지원하지 않지만, 클로저를 활용하면 비공개 메서드를 효과적으로 흉내 낼 수 있습니다.

예제 코드

var counter = (() => {
    var privateCounter = 0;
    let changeBy = (val) => privateCounter += val;
    return {
        increment: () => changeBy(1),
        decrement: () => changeBy(-1),
        value: () => privateCounter
    };
})();
console.log(counter.value()); // 0
counter.increment();
counter.increment();
console.log(counter.value()); // 2
counter.decrement();
console.log(counter.value()); // 1

출력 결과

0
2
1

위 예제에서 privateCounterchangeBy는 외부에서 직접 접근할 수 없는 비공개 상태로 유지되며, 오직 반환된 객체의 increment, decrement, value 메서드를 통해서만 조작할 수 있습니다. 이것이 클로저를 활용한 데이터 은닉(Data Encapsulation)의 대표적인 패턴입니다.

2. 이벤트 핸들러에서 클로저 활용하기

반복문 안에서 여러 버튼에 이벤트 리스너를 등록할 때도 클로저는 매우 유용합니다. 다음은 잘못된 작성 방식의 예입니다.

for(let i = 0; i < 4; i++) {
    button = buttons[i]
    button.addEventListener("click", alert(i)) // 잘못된 방식
}

alert(i)처럼 함수를 괄호와 함께 작성하면, 이벤트가 발생하기 전인 반복문 실행 시점에 즉시 호출되어 그 반환값(undefined)이 핸들러로 등록됩니다. 결과적으로 버튼을 클릭해도 아무런 동작이 일어나지 않게 됩니다.

해결 방법은 각 반복마다 i 값을 기억하는 클로저를 형성하는 것입니다.

for(let i = 0; i < 4; i++) {
    button = buttons[i]
    // 화살표 함수로 감싸 클로저를 형성
    button.addEventListener("click", () => alert(i))
}

이렇게 화살표 함수로 감싸주면 각 버튼의 클릭 핸들러는 자신이 생성된 시점의 i 값을 클로저로 캡처하여 기억합니다. 덕분에 첫 번째 버튼을 클릭하면 0, 두 번째 버튼을 클릭하면 1처럼 각 버튼이 올바른 인덱스를 출력하게 됩니다.

마무리

클로저는 단순히 면접에서 자주 묻는 이론적 개념이 아니라, 데이터 은닉, 상태 유지, 콜백 함수 작성 등 실무에서 폭넓게 활용되는 자바스크립트의 강력한 기능입니다. 클로저의 동작 원리를 정확히 이해하면 더 안전하고 우아한 코드를 작성할 수 있습니다.