본문 바로가기

Study/JavaScript

코어 자바스크립트 - 클로저(Closure)

클로저는내부함수와 LexicalEnvironment의 조합으로 함수가 생성될 때면 매번 같이 발생합니다.

이 말을 좀 더 풀어보겠습니다.

컨텍스트 A에서 선언한 변수(environmentRecord)를 내부함수 B에서 outerEnvironmentReference가 참조합니다. 이때 발생하는 특별한 현상을 클로저라고 합니다.

그러면 특별한 현상이라는 것이 무엇일까요?

 

코드를 보겠습니다.

// 기본 코드
var outer = function () {
	var a = 1;
	var inner = function () {
      console.log(++a);
    };
    inner();
 };
 outer();
 
 // 클로저가 생긴 코드
 // 실행하지 않고 return으로 값을 넘김
 // outer함수를 outer2로 넘겨줌
 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()로 inner가 반환되며 outer2를 실행시키면 inner가 작동하며 outerEnvironmentReference로 outer의 a를 참조해 값을 계산해 2가 나오게 됩니다. a는 2가 되고 inner에서도 참조만 하고 있기 때문에 a는 2가 됩니다. 여기서 outer2()를 또 실행하면 a가 2인 값에 1을 더해 3이 출력되어집니다.

a변수는 전역 컨텍스트가 끝날 때까지 살아남습니다. outer2가 inner를 붙잡고 있고 inner 함수의 outerEnviromentReference로부터 a를 참조하기 때문입니다.. 

이를 해결하기 위해서는 outer2 변수에 다른 것을 대입하면 됩니다.

 

이처럼 outer에 있는 변수 a가 사라지지 않고 계속 살아있는 것이 특별한 현상입니다. 정리하자면 컨텍스트 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); // '나'

name이라는 프로퍼티 값이 없는 대신에 getter로 name을 가지고 있기 때문에 실행되며 return으로 _name을 반환합니다. 그래서 '나'가 출력됩니다.

이때 user에서 _name의 역할은 끝나 사라져야 했으나 내부함수 name이 변수로 _name을 가지고 있기 때문에 나중에 쓸 변수로 판단하고 _name 변수를 살려둡니다.

 

roy.name = '제이';
console.log(roy.name); // '제이'

이번에는 roy.name 값을 제이로 바꾸려고 하면 set.name이 실행돼서 _name의 값이 '제이'로 바뀌게 됩니다. 이어서 get을 실행해 '제이'를 반환해 제대로 동작하고 있음을 알 수 있습니다.

 

roy._name = '로이';
console.log(roy.name); // '제이'

roy 객체에 _name 프로퍼티가 없습니다. 그래서 새로 만들어냅니다. 즉, _name 변수에는 어떤 영향도 끼치지 못하기 때문에 출력되는 값은 여전히 제이가 됩니다.

 

console.log(roy.status); // 'login'

roy.status를 출력하면 user 함수에 선언된 _logged 변수의 값에 따라 login, logout을 반환합니다.  _logged는 true라고 앞서 선언했기 때문에 login이 출력됩니다.

이때 _logged 역시 내부함수의 변수로 남아있기 때문에 사라지지 않습니다.

 

roy.logout();
console.log(roy.status); // 'logout'

roy.logout을 실행하면 _logged가 false로 바뀌면서 roy.status를 출력했을 때 logout이 반환됩니다.

 

roy.status = true;
console.log(roy.status); // 'logout'

roy에는 getter는 있지만 setter가 없기 때문에 무시도되어 변경이 되지 않아 그대로 logout이 반환됩니다.

 

이번 예시를 통해 알아본 것들을 정리해보겠습니다.

  1.  함수 종료 후에도 사라지지 않고 값을 유지하려는 변수 - 클로저의 핵심 개념

  2. 외부로부터 내부 변수 보호 (캡슐화)

 

 

지금까지 클로저에 대해 알아보았습니다.

함수 종료 후에도 사라지지 않는 지역변수를 만들 수 있다는 것이 클로저의 개념이라는 것을 기억하시면 될 것 같습니다.

728x90