IT 개발 라이프/Front-End

자바스크립트 클로저(Closure)에 대해 알아보자! 🔒

10Biliion 2024. 12. 9. 16:23

1. 클로저란 무엇인가요? 🤔

**클로저(Closure)**란 함수와 그 함수가 선언될 당시의 **렉시컬 환경(Lexical Environment)**을 함께 기억하는 구조를 말합니다. 간단히 말해, 함수가 자신이 생성될 때의 외부 변수에 접근할 수 있는 기능입니다.

function outer() {
  let outerVar = "I am from outer";

  function inner() {
    console.log(outerVar); // 외부 함수의 변수에 접근 가능
  }

  return inner;
}

const myClosure = outer();
myClosure(); // 출력: I am from outer

위 코드에서 inner 함수는 outer 함수가 종료된 이후에도 outerVar에 접근할 수 있습니다. 이것이 클로저입니다!


2. 클로저의 동작 원리 🔍

자바스크립트는 함수가 호출될 때마다 실행 컨텍스트를 생성하고, 함수 내부의 변수와 외부 변수의 참조를 저장합니다. 클로저는 이러한 참조를 유지하여 함수가 종료된 이후에도 외부 변수를 계속 사용할 수 있도록 합니다.

2.1 렉시컬 스코프와 클로저

자바스크립트는 렉시컬 스코프(Lexical Scope)를 따릅니다. 즉, 함수가 어디서 호출되었는지가 아니라, 어디서 정의되었는지에 따라 스코프가 결정됩니다.

function outer() {
  let name = "JavaScript";

  return function inner() {
    console.log(`Hello, ${name}!`); // 렉시컬 환경의 변수 참조
  };
}

const greet = outer();
greet(); // 출력: Hello, JavaScript!

greet 함수는 outer 함수가 호출될 당시의 환경을 기억하고 있습니다.


3. 클로저 활용 예시 🌟

3.1 private 변수 생성하기

클로저를 사용하면 외부에서 직접 접근할 수 없는 private 변수를 만들 수 있습니다.

function createCounter() {
  let count = 0; // private 변수

  return {
    increment() {
      count++;
      console.log(count);
    },
    decrement() {
      count--;
      console.log(count);
    },
  };
}

const counter = createCounter();
counter.increment(); // 출력: 1
counter.increment(); // 출력: 2
counter.decrement(); // 출력: 1

count 변수는 createCounter 함수 내부에서만 접근할 수 있으며, 외부에서는 직접 수정할 수 없습니다.

3.2 한 번만 실행되는 함수 (IIFE)

즉시 실행 함수 표현식(IIFE)과 클로저를 결합하여 한 번만 실행되는 코드를 작성할 수 있습니다.

const initialize = (function () {
  let isInitialized = false;

  return function () {
    if (!isInitialized) {
      console.log("초기화 중...");
      isInitialized = true;
    } else {
      console.log("이미 초기화되었습니다.");
    }
  };
})();

initialize(); // 출력: 초기화 중...
initialize(); // 출력: 이미 초기화되었습니다.

3.3 반복문에서의 클로저 활용

클로저를 사용하여 반복문에서 값의 참조를 유지할 수 있습니다.

function createFunctions() {
  let functions = [];

  for (let i = 0; i < 3; i++) {
    functions.push(function () {
      console.log(i);
    });
  }

  return functions;
}

const funcs = createFunctions();
funcs[0](); // 출력: 0
funcs[1](); // 출력: 1
funcs[2](); // 출력: 2

위 예제에서는 let을 사용했기 때문에 각 반복에서 새로운 렉시컬 환경이 생성됩니다. 따라서 클로저가 각기 다른 값을 기억합니다.


4. 클로저 사용 시 주의할 점 ⚠️

4.1 메모리 누수

클로저는 참조를 유지하기 때문에 메모리 누수를 유발할 수 있습니다. 더 이상 필요하지 않은 클로저는 참조를 제거하여 가비지 컬렉터가 처리할 수 있도록 해야 합니다.

function outer() {
  let largeData = new Array(1000000).fill("📦");

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

const closure = outer();
// closure가 더 이상 필요 없다면 null로 설정
closure = null;

4.2 과도한 사용

클로저를 너무 많이 사용하면 코드의 복잡도가 증가할 수 있으므로, 필요할 때만 사용하는 것이 좋습니다.


마무리📜

  • 클로저는 함수와 그 함수가 선언될 당시의 렉시컬 환경을 함께 기억하는 구조입니다.
  • 외부 함수의 변수를 참조할 수 있으며, private 변수와 같은 다양한 패턴을 구현할 수 있습니다.
  • 클로저를 사용할 때 메모리 누수와 코드 복잡성에 주의하세요.