JS Closuer

KB LEE·2025년 2월 21일
0

JavaScript 펼쳐보기

목록 보기
3/5
post-thumbnail

목적

이 문서는 자바스크립트 클로저가 무엇이고, 클로저를 통해 어떤 것을 수행할 수 있는지 정리한 문서입니다.


Closure?

Closure란?

클로저(Closure)는 JavaScript에서 함수가 선언될 당시의 렉시컬 스코프(Lexical Scope)를 기억하고, 해당 스코프에 접근할 수 있는 함수를 의미합니다.

즉, 내부 함수가 외부 함수의 변수에 접근할 수 있으며, 외부 함수의 실행이 끝난 후에도 내부 함수가 해당 변수를 계속 유지할 수 있는 개념입니다.

  • 내부 함수외부 함수의 변수에 접근 가능
  • 외부 함수 실행이 끝나도 변수 값이 유지됨

Closure의 핵심 개념

  1. Lexical Environment
  2. Execution Context

Closure의 핵심 개념은 이전 “Execution Context”에 대해 설명하는 글에서 봐왔던 단어들입니다.
두 내용을 이해하고 있다면 글을 보기 전부터 동작원리를 예상하실 수도 있습니다.

더 수월하게 읽기 위해선 Execution Context 글을 먼저 읽고 오시는 것이 이해에 도움이 됩니다.


동작원리

Execution Context와 Lexical Environment

JavaScript는 함수가 실행될 때 Execution Context와 Lexical Environment을 생성합니다.

내부 함수가 외부 함수의 변수에 접근할 수 있는 이유는 Lexical Environment에 의해 참조가 유지되기 때문입니다.

이를 통해 Closure는 실행이 끝난 외부 함수의 변수에 접근할 수 있게 됩니다.

→ 내부 Execution Context가 외부 Execution Context에 대한 참조를 유지하고 있기때문에

생명주기

보통함수가 실행되면 로컬 변수는 가비지 컬렉션(GC)에 의해 제거됩니다.

그러나 Closure는 내부 함수가 외부 변수를 참조하면 이를 메모리에 유지시킵니다.

코드예시

간단한 코드 예시와 함께 확인해보겠습니다.

function outer() {
    let count = 0;

    return function inner() {
        count++;
        console.log(count);
    };
}

const counter = outer();
counter(); // 1
counter(); // 2

여기서 counter를 실행시킬 때 마다 출력되는 값이 1씩 증가하는 것을 볼 수 있습니다.

inner를 통해 실행이 끝난 외부함수인 outer의 변수에 접근할 수 있고, 메모리애 유지되고 있다는 사실을 확인할 수 있습니다.


용도

Q. 그럼 Closure는 언제 사용하는건가요?

각 용도를 코드와 함께 알아보겠습니다.

1. 데이터은닉(캡슐화) 및 모듈패턴

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
counter.decrement(); // 1
console.log(counter.count); // undefined (외부에서 직접 접근 불가)

count 변수는 외부에서 직접 변경할 수 없으며, 오직 제공된 메서드를 통해서만 변경이 가능

코드상에선 직접 접근할 수 없고, 메모리와 엔진내부로직을 통해 관리

2. State 관리 및 유지

function attachEventHandler() {
    let clickCount = 0;

    document.getElementById("myButton").addEventListener("click", function() {
        clickCount++;
        console.log(`Button clicked ${clickCount} times`);
    });
}

attachEventHandler();

clickCount를 유지하며, 버튼 클릭 횟수를 저장


Closure 사용시 주의사항

Q. 위 내용을 쭉 보니, 굉장히 유용한데 막 쓰면 좋은거죠?

물론 과유불급입니다. Closure를 사용할때도 주의사항들이 있습니다.

메모리 누수 (Memory Leak)

불필요한 변수를 유지하면 가비지 컬렉터가 제거하지 못하여 메모리 누수가 발생할 수 있습니다.

이후 가비지 컬렉터(GC)와 여러가지 메모리 누수 상황에 대해서는 포스팅으로 돌아오겠습니다. 🙂

Q. 그렇다면 언제 메모리 누수가 발생하나요?

몇가지 예시 코드와 함께 알아보겠습니다

첫번째, 불필요한 데이터를 유지

function createClosure() {
    let bigData = new Array(1000000).fill("data");

    return function() {
        console.log(bigData[0]);
    };
}

// 메모리를 할당한 상태
// bigData변수는 여전히 참조되고 있기때문에 해제되지 않습니다
const closureFunc = createClosure();

// Required: 사용 후 참조 해제
closureFunc = null;

사용완료 이후에도 붎필요한 데이터를 계속 유지되는 상태입니다.

마지막 줄과 같이 사용이 완료되면 참조를 해제해야합니다.

두번째, DOM요소를 참조하는 경우

function saveElementReference() {
    let element = document.getElementById("myDiv");

    return function() {
        console.log(element.innerHTML);
    };
}

const getElementContent = saveElementReference();
document.getElementById("myDiv").remove(); // DOM에서 삭제

위 경우는 얼핏보면 해제가 됐다고 생각할 수도 았습니다.

하지만 DOM자체는 삭제됐지만 getElementContent는 element를 계속 참조하며, GC에서 수집되지 않습니다.

이럴 경우에도 위에서 말했듯이 침조를 해제해주어야합니다.

function saveElementReference() {
    let element = document.getElementById("myDiv");

    return function() {
        if (!document.body.contains(element)) {
            element = null; // 요소가 제거되었으면 참조 해제
        }
        console.log(element?.innerHTML);
    };
}

const getElementContent = saveElementReference();
document.getElementById("myDiv").remove();

과도한 사용

클로저는 변수를 메모리에 유지하기 때문에, 너무 많이 사용하면 불필요한 메모리 소비가 증가를 초래


정리 및 후기

오늘 Closure 개념과 Execution Context에 의한 동작 원리를 깊이 있게 탐구했습니다.

Closure는 개발할 때 자주 마주치는 개념이지만, 그동안 내부 작동 원리를 정확히 이해하지 못한 채 사용했던 경우가 많았습니다.

하지만 이번 정리를 통해 Closure가 Execution Context와 Lexical Environment를 기반으로 어떻게 동작하는지, 그리고 이러한 원리가 실제 코드에서 어떤 영향을 미치는지를 명확히 이해할 수 있었습니다.

다음에도 더 알차고 깔끔한 내용으로 찾아뵙겠습니다.

읽어주셔서 감사합니다!

profile
한 발 더 나아가자

0개의 댓글