javascript

[CoreJavascript] 5과 클로저(closure)

프로일기꾼 2025. 2. 15. 18:51

클로저(Closure)

  • MDN says: A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). ... In Javascript, closures are created every time a function is created, at function creation time.
  • 내부 함수와 LexicalEnvironment의 조합
    • <=> 실행 컨텍스트 A 내에서 함수 B를 생성한 경우, A(실행 컨텍스트)의 lexical environment와 내부함수 B의 조합에서 나타나는 특별한 현상
    • <=> 컨텍스트 A에서 선언한 변수를 내부함수 B에서 참조할 경우에 발생하는 특별한 현상
    • var outer = function() { var a = 1; var inner = function() { console.log(++a); } inner(); } outer();
    • 위 예제에서 클로저를 사용해 특별한 현상이라고 확인할 수 있는 부분은 딱히 없다.
    • var outer = function () { var a = 1; var inner = function() { return ++a; } return inner; } var outer2 = outer(); console.log(outer2()); // 2 console.log(outer2()); // 3
    • 위 예제에서 outer함수 호출될 때 call stack
      • outer 함수는 return 된 것이 없고, 아직 실행 중이기에 outer2 변수는 undefined.
    • outer 함수가 종료된 이후 (outer 함수가 종료되었지만 아래와 같이 outer 컨텍스트 내에 a라는 변수는 여전히 유효한 상태.)
      • a 라는 변수는 inner 함수에서 사용되고, inner 함수는 outer2에서 참조하고 있는 상황.
      • 곧 a의 참조 카운트가 0이 아닌 상황.
    • 첫번째 outer2 함수가 호출 되었을 때
    • 첫번째 outer2 함수가 종료 되었을 때
    • 두번째 outer2 함수가 호출 되었을 때

클로저의 핵심

  • 컨텍스트 A에서 선언한 변수 a를 참조하는 내부함수 B를 A의 외부로 전달할 경우, A가 종료된 이후에도 a가 사라지지 않는 현상
  • 지역변수가 함수 종료 후에도 사라지지 않게 할 수 있다.
  • <=> 함수 종료 후에도 사라지지 않는 지역변수를 만들 수 있다.

클로저의 진가를 느낄 수 있는 예제

function user(_name) {
  var _logged = true;
  return {
    get name() { return _name },
    set name(v) { _name = v; },
    login() { _logged = true },
    logout() { _logged = false },
    get status() {
      return _logged ? 'login' : 'logout';
    },
  }
}

var roy = user('재남');

console.log(roy.name); 
// 재남 출력, roy객체에는 name 속성이 없음에도 값이 출력되는 이유는 name에 대한 getter가 있어 getter가 호출이 되기 때문이다.
// getter안에 _name 변수는 user 함수를 호출할 때 매개변수로 넘겨받은 값을 갖고 있는, user함수에서 선언된 변수이다. 원래는 user 함수가 종료될 때, 같이 사라졌어야할 변수이지만, user 함수가 return하는 부분에 _name이란 변수를 사용하는 곳이 있기에(곧 참조 카운트가 0이 아닌 상태이기에) _name이 유효하게 된다.
// 함수는 죽었지만, 변수는 끈질기게 살아남은 상황. 그렇기에 '재남'이 출력되는 것이다.

roy.name = '제이' // name에 대한 setter가 호출된다.
console.log(roy.name) // '제이' 출력

roy._name = '케이' // roy 객체 안에 _name이란 프로퍼티가 존재하지 않는다.
console.log(roy.name) // '제이' 출력, name getter에 대한 호출 결과는 동일하게 '제이'

console.log(roy.status) // status 에 대한 getter 호출되어 'login' 출력

roy.logout();
console.log(roy.status) // logout 출력

roy.status = true; // status에 대한 setter가 없기에 해당 명령은 무시된다.
console.log(roy.status) // 위 명령은 무시되기에 여전히 logout 출력

위 예제를 통해 아래의 내용을 확인할 수 있다.

  1. 함수 종료 후에도 사라지지 않고 값을 유지하는 변수(_name, _logged)
  2. 외부로부터 내부 변수 보호(캡슐화)
    • _logged라는 변수는 login, logout 함수를 통해서만 수정 가능

Todo

  • 클로저에 의한 메모리 누수를 관리하는 방법
  • 클로저를 활용한 다양한 프로그래밍 기법