[JS] 클로저(Closure)

javaScript 기초를 탄탄하게 다져보자

Posted by lim.Chuck on December 23, 2024

[JavaScript]

기초

  1. [JS] Js기초
  2. [JS] Js기초 변수
  3. [JS] Js기초 자료형
  4. [JS] Js기초 형변환
  5. [JS] Js기초 연산자
  6. [JS] Js기초 반복문
  7. [JS] Js기초 switch
  8. [JS] Js기초 function
  9. [JS] Js기초 객체
  10. [JS] Js기초 배열

중급

  1. [JS] Js중급 호이스팅(Hoisting)과 TDZ(Temporal Dead Zone)
  2. [JS] Js중급 생성자함수
  3. [JS] 객체 메소드(Object methods), 계산된 프로퍼티(Computed property)
  4. [JS] 심볼(Symbol)
  5. [JS] WeakMap WeakSet
  6. [JS] 숫자, 수학 method (Number, Math)
  7. [JS] 문자열 메소드(String methods)
  8. [JS] 배열 메소드(Array methods)
  9. [JS] 구조 분해 할당 (Destructuring assignment)
  10. [JS] 매개변수 리스트와 전개 문법(Rest parameters and spread syntax)
  11. [JS] 클로저(Closure)
  12. [JS] setTimeout / setInterval
  13. [JS] call / apply / bind
  14. [JS] 상속, 프로토타입(Prototype)
  15. [JS] 클래스(Class)
  16. [JS] 클로미스(Promise)
  17. [JS] 비동기 처리(Async/Await)
  18. [JS] Generator
  19. [JS] 메모리 릭(Memory Leak)

클로저! 함수와 그 함수가 선언된 렉시컬 환경의 조합이야! 🎯

1. 클로저 기본 개념

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 기본적인 클로저
function createCounter() {
  let count = 0; // 외부에서 직접 접근 불가능한 변수

  return {
    increase() {
      return ++count;
    },
    decrease() {
      return --count;
    },
    getCount() {
      return count;
    },
  };
}

const counter = createCounter();
console.log(counter.increase()); // 1
console.log(counter.increase()); // 2
console.log(counter.decrease()); // 1
console.log(counter.getCount()); // 1

// count 변수에 직접 접근 불가
console.log(counter.count); // undefined

2. 클로저를 활용한 프라이빗 변수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function createWallet(initialBalance) {
  let balance = initialBalance; // 프라이빗 변수

  return {
    deposit(amount) {
      if (amount > 0) {
        balance += amount;
        return `${amount}원이 입금되었습니다. 잔액: ${balance}원`;
      }
      return "유효하지 않은 입금액입니다.";
    },

    withdraw(amount) {
      if (amount > 0 && balance >= amount) {
        balance -= amount;
        return `${amount}원이 출금되었습니다. 잔액: ${balance}원`;
      }
      return "출금이 불가능합니다.";
    },

    getBalance() {
      return `현재 잔액: ${balance}원`;
    },
  };
}

const myWallet = createWallet(1000);
console.log(myWallet.deposit(500)); // "500원이 입금되었습니다. 잔액: 1500원"
console.log(myWallet.withdraw(200)); // "200원이 출금되었습니다. 잔액: 1300원"
console.log(myWallet.getBalance()); // "현재 잔액: 1300원"

3. 클로저를 이용한 모듈 패턴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const userModule = (function () {
  // 프라이빗 변수
  let users = [];

  // 프라이빗 함수
  function validateUser(user) {
    return user.name && user.age;
  }

  // 퍼블릭 API
  return {
    addUser(user) {
      if (validateUser(user)) {
        users.push(user);
        return "사용자가 추가되었습니다.";
      }
      return "유효하지 않은 사용자입니다.";
    },

    getUsers() {
      return [...users]; // 배열의 복사본 반환
    },

    findUser(name) {
      return users.find((user) => user.name === name);
    },
  };
})();

userModule.addUser({ name: "김코딩", age: 20 });
console.log(userModule.getUsers()); // [{name: "김코딩", age: 20}]
console.log(userModule.users); // undefined (프라이빗)

4. 클로저와 커링

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function multiply(a) {
  return function (b) {
    return function (c) {
      return a * b * c;
    };
  };
}

// 커링된 함수 사용
const mult2 = multiply(2); // a = 2로 고정
const mult2_3 = mult2(3); // b = 3으로 고정
console.log(mult2_3(4)); // 2 * 3 * 4 = 24

// 한 번에 호출
console.log(multiply(2)(3)(4)); // 24

// 실용적인 예제
function formatPrice(currency) {
  return function (price) {
    return `${currency}${price.toFixed(2)}`;
  };
}

const formatUSD = formatPrice("$");
const formatKRW = formatPrice("");

console.log(formatUSD(123.456)); // "$123.46"
console.log(formatKRW(1000)); // "₩1000.00"

5. 클로저와 이벤트 핸들러

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function createButtonCounter() {
  let count = 0;

  function updateDisplay() {
    console.log(`클릭 횟수: ${count}`);
  }

  return function handleClick() {
    count++;
    updateDisplay();
  };
}

// DOM 이벤트 리스너에서 사용
const button = document.createElement("button");
button.textContent = "클릭하세요";
button.addEventListener("click", createButtonCounter());

6. 클로저를 활용한 메모이제이션

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function memoize(fn) {
  const cache = new Map();

  return function (...args) {
    const key = JSON.stringify(args);

    if (cache.has(key)) {
      console.log("캐시된 결과 사용");
      return cache.get(key);
    }

    const result = fn.apply(this, args);
    cache.set(key, result);
    console.log("새로운 결과 계산");
    return result;
  };
}

// 피보나치 수열 계산
const fibonacci = memoize(function (n) {
  if (n < 2) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
});

console.log(fibonacci(10)); // 처음 계산
console.log(fibonacci(10)); // 캐시된 결과

꿀팁! 🍯

  1. 메모리 관리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// BAD: 메모리 누수 가능성
function createBadClosure() {
  const largeData = new Array(1000000);
  return function () {
    console.log(largeData.length);
  };
}

// GOOD: 필요한 데이터만 유지
function createGoodClosure() {
  const largeData = new Array(1000000);
  const dataLength = largeData.length;
  return function () {
    console.log(dataLength);
  };
}
  1. 성능 최적화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 클로저 재사용
const createLogger = (prefix) => {
  // 클로저는 한 번만 생성
  const logger = (message) => {
    console.log(`${prefix}: ${message}`);
  };

  return logger;
};

const debugLog = createLogger("DEBUG");
const errorLog = createLogger("ERROR");

debugLog("테스트 메시지"); // "DEBUG: 테스트 메시지"
errorLog("오류 발생!"); // "ERROR: 오류 발생!"

이제 클로저는 마스터! 😎 프라이빗 변수 관리와 상태 유지가 필요한 상황에서 유용하게 사용할 수 있어!