클로저(Closure)

2025. 3. 30. 22:17Web Development/JavaScript

클로저(Closure)

클로저는 함수와 그 함수가 선언된 렉시컬 환경의 조합입니다. 함수가 자신이 생성된 스코프 밖에서 실행될 때도 원래 스코프에 접근할 수 있는 현상을 말합니다.

클로저의 기본 개념

function createCounter() {
  let count = 0; // 외부에서 직접 접근할 수 없는 변수
  
  return function() {
    count++; // 외부 함수의 변수에 접근
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 1 출력
console.log(counter()); // 2 출력
console.log(counter()); // 3 출력

// 새로운 카운터 생성 (독립적인 count 변수를 가짐)
const counter2 = createCounter();
console.log(counter2()); // 1 출력 (counter와 별개의 count 변수)

위 예제에서 createCounter 함수는 이미 실행이 끝났지만, 반환된 함수는 여전히 count 변수에 접근할 수 있습니다. 이것이 클로저의 핵심입니다.

클로저의 활용 예제

1. 데이터 캡슐화와 정보 은닉

function createBank() {
  let balance = 0; // 외부에서 직접 접근 불가능한 변수
  
  return {
    deposit: function(amount) {
      if (amount > 0) {
        balance += amount;
        return `${amount}원을 입금했습니다. 잔액: ${balance}원`;
      }
      return "유효하지 않은 금액입니다.";
    },
    withdraw: function(amount) {
      if (amount > 0 && amount <= balance) {
        balance -= amount;
        return `${amount}원을 출금했습니다. 잔액: ${balance}원`;
      }
      return "출금할 수 없습니다.";
    },
    getBalance: function() {
      return `현재 잔액: ${balance}원`;
    }
  };
}

const myAccount = createBank();
console.log(myAccount.deposit(5000)); // "5000원을 입금했습니다. 잔액: 5000원" 출력
console.log(myAccount.withdraw(2000)); // "2000원을 출금했습니다. 잔액: 3000원" 출력
console.log(myAccount.getBalance()); // "현재 잔액: 3000원" 출력
// console.log(myAccount.balance); // undefined (직접 접근 불가)

2. 함수 팩토리

function createMultiplier(factor) {
  return function(number) {
    return number * factor;
  };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);
const quadruple = createMultiplier(4);

console.log(double(5)); // 10 출력
console.log(triple(5)); // 15 출력
console.log(quadruple(5)); // 20 출력

3. 이벤트 핸들러와 콜백

function setupButton(buttonId, message) {
  // 버튼 클릭 시 message 변수에 접근하는 클로저 생성
  document.getElementById(buttonId).addEventListener('click', function() {
    alert(message);
  });
}

setupButton('button1', '버튼 1을 클릭했습니다!');
setupButton('button2', '버튼 2를 클릭했습니다!');

4. 모듈 패턴

const calculator = (function() {
  // 비공개 변수와 함수
  let result = 0;
  
  function validate(num) {
    return typeof num === 'number' && !isNaN(num);
  }
  
  // 공개 API
  return {
    add: function(num) {
      if (validate(num)) {
        result += num;
      }
      return this; // 메서드 체이닝을 위한 this 반환
    },
    subtract: function(num) {
      if (validate(num)) {
        result -= num;
      }
      return this;
    },
    multiply: function(num) {
      if (validate(num)) {
        result *= num;
      }
      return this;
    },
    divide: function(num) {
      if (validate(num) && num !== 0) {
        result /= num;
      }
      return this;
    },
    getResult: function() {
      return result;
    },
    reset: function() {
      result = 0;
      return this;
    }
  };
})(); // 즉시 실행 함수로 모듈 생성

console.log(calculator.add(5).multiply(2).subtract(3).getResult()); // 7 출력
calculator.reset();
console.log(calculator.getResult()); // 0 출력

클로저와 관련된 주의사항

1. 메모리 관리

클로저는 외부 함수의 변수를 참조하기 때문에 해당 변수들은 가비지 컬렉션에서 제외됩니다. 따라서 불필요한 클로저 사용은 메모리 누수를 일으킬 수 있습니다.

// 메모리 누수 가능성이 있는 코드
function createHugeArray() {
  const hugeArray = new Array(1000000).fill("데이터");
  
  return function(index) {
    return hugeArray[index];
  };
}

const getItem = createHugeArray(); // hugeArray 전체가 메모리에 유지됨

2. 루프에서의 클로저

루프 내에서 클로저를 생성할 때는 각 반복마다 새로운 렉시컬 환경이 필요합니다.

// 문제가 있는 코드 (var 사용)
function createButtons() {
  for (var i = 1; i <= 5; i++) {
    var button = document.createElement("button");
    button.textContent = "Button " + i;
    button.addEventListener("click", function() {
      console.log("Button " + i + " clicked"); // 모든 버튼이 "Button 6 clicked" 출력
    });
    document.body.appendChild(button);
  }
}

// 해결책 1: 즉시 실행 함수로 새로운 스코프 생성
function createButtonsFixed1() {
  for (var i = 1; i <= 5; i++) {
    (function(index) {
      var button = document.createElement("button");
      button.textContent = "Button " + index;
      button.addEventListener("click", function() {
        console.log("Button " + index + " clicked"); // 정상 작동
      });
      document.body.appendChild(button);
    })(i);
  }
}

// 해결책 2: let 사용 (블록 스코프)
function createButtonsFixed2() {
  for (let i = 1; i <= 5; i++) {
    const button = document.createElement("button");
    button.textContent = "Button " + i;
    button.addEventListener("click", function() {
      console.log("Button " + i + " clicked"); // 정상 작동
    });
    document.body.appendChild(button);
  }
}

'Web Development > JavaScript' 카테고리의 다른 글

객체  (0) 2025.03.30
함수 패턴  (0) 2025.03.30
스코프  (0) 2025.03.30
함수의 정의와 호출  (0) 2025.03.30
반복문  (0) 2025.03.30