클로저

코딩덕·2023년 6월 8일

💡 클로저란?

함수가 선언될 당시의 스코프(환경)를 기억하고, 함수가 실행될 때에도 해당 스코프에 접근할 수 있는 개념

즉, 내부 함수가 외부 함수의 변수를 참조할 수 있도록 유지되는 현상이다.

'클로저'는 함수가 실행 콘텍스트 스택에서 제거가 되더라도, 렉시컬 환경과 함께 함수가 선언될 당시의 환경을 기억했다가 나중에 호출되었을때 원래의 환경에 따라 수행되는 함수를 의미한다.


클로저 예시

const x = 1;

function outer() {
  const x = 10;
  const inner = function () {
    console.log(x); 
  }; 
  return inner;
}

const getx = outer(); // 1️⃣
getx(); // 10 // 2️⃣

outer함수를 호출하면(1️⃣) outer함수는 중첩함수 inner을 반환하고 생명주기를 마감한다. (실행컨텍스트에서 제거). 이때 outer함수의 지역변수 x와 변수 값 10을 저장하고 있던 outer함수의 실행컨텍스트가 제거되었으므로 outer함수의 지역변수 x 또한 생명주기를 마감한다.

따라서, x변수에 접근할 수 있는 방법은 없어보인다.

하지만 위 코드의 실행결과(2️⃣)는 outer함수의 지역변수인 10이다. 이미 생명주기가 종료되어 실행컨텍스트 스택에서 제거된 outer함수의 지역변수가 다시 부활이라도 한듯이 동작하고있다. 중첩함수 inner가 이미 생명주기를 마감한 outer함수 속 내부함수의 지역변수 x를 참조할 수 있다면 이때의 inner함수를 클로저라고 한다.

이처럼 외부 함수보다 중첩함수가 더 오래 유지되는 경우 중첩함수는 이미 생명주기가 종료한 외부 함수의 변수를 참조할 수 있는데 이런 중첩함수를 '클로저' 라고 한다.




클로저의 사용이유

클로저를 사용하면 전역변수 사용을 줄이고, 비슷한 코드의 재사용률을 높여 아래와 같은 이득을 볼 수 있다. 또한 이러한 특성으로, 객체지향 프로그래밍과 밀접한 관계에 있다.

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

2. 상태 유지 가능

함수가 종료된 후에도 변수를 유지할 수 있어 상태를 관리할 때 유용함

function scoreTracker() {
  let score = 0;

  return function () {
    score += 10;
    console.log(`Current Score: ${score}`);
  };
}

const updateScore = scoreTracker();
updateScore(); // 10
updateScore(); // 20
updateScore(); // 30

3. 모듈 패턴을 활용한 코드 구조화

전역 네임스페이스 오염을 방지하고, 재사용 가능한 모듈을 만들 수 있음

const Module = (function () {
  let privateVar = "secret";

  return {
    getSecret: function () {
      return privateVar;
    },
    setSecret: function (newSecret) {
      privateVar = newSecret;
    },
  };
})();

console.log(Module.getSecret()); // "secret"
Module.setSecret("new secret");
console.log(Module.getSecret()); // "new secret"




클로저의 단점

클로저를 사용하면 장점이 많지만, 메모리 누수와 같은 단점도 존재한다.

클로저는 참조하는 변수들을 GC(Garbage Collector)가 자동으로 제거하지 않는다.

따라서, 필요 없는 데이터가 계속 유지되면 메모리 누수가 발생할 수 있다.

function createBigClosure() {
  let hugeArray = new Array(1000000).fill("data");
  
  return function () {
    console.log(hugeArray[0]); // 외부 변수를 계속 참조 → 메모리 점유 지속
  };
}

const bigFunc = createBigClosure();
bigFunc(); // "data"
// bigFunc를 더 이상 사용하지 않으면 메모리를 해제해야 함!

따라서 클로저가 더 이상 필요하지 않을 경우, 아래와 같이 변수를 null로 할당하여 참조를 끊어줄 필요가 있다.

let bigFunc = createBigClosure();
bigFunc(); // "data"
bigFunc = null; // GC가 메모리 해제할 수 있도록 참조 해제

0개의 댓글