클로저

2 minute read


클로저(closure)

내부함수가 외부함수의 맥락에 접근할 수 있는 것을 가르킨다. 클로저는 자바스크립트를 이용한 고난이도의 테크닉을 구사하는데 필수적인 개념으로 활용된다.

// 외부함수
function outter() {
  var title = "Hello JS";
  // 내부함수
  function inner() {
    alert(title);
  }
  // 위 함수 호출
  inner();
}

outter();

어떤 함수의 내부에서만 사용될 함수를 내부함수로 만들게 되면 함수의 응집력이 높아지고 다른 개입이 줄어들어 안정성이 생기게 된다.

위 코드를 보면 내부함수에서 외부함수의 지역변수에 접근하고 있다.

이런 관계는 외부함수의 실행이 끝나서 소멸된 이후에도 내부함수가 외부함수의 변수에 접근할 수 있다.

// 외부함수
function outter() {
  var title = "Hello JS";
  return // 내부함수
  function() {
    alert(title);
  }
}

// outter함수 호출
var inner = outter();
inner();

inner에 outter 함수를 담으면서 호출될 때 return으로 외부함수의 지역변수는 소멸되는것이 당연하지만 inner()를 호출하게 되면 “Hello JS”가 출력된다. 즉 외부함수의 title이 소멸되지 않고 내부함수를 통해서 외부함수의 지역변수에 접근할 수 있다는 것이고 내부함수가 소멸될 때까지 이 지역변수 또한 소멸되지 않는 특성이란걸 알 수 있다. 이러한 메커니즘을 클로저라고 한다.


클로저 예제1

  • private variable
    소프트웨어가 커지는 과정에서 어떤 정보가 있을 때 그 정보를 아무나 수정하는걸 방지하는걸 말한다.

    // 객체를 반환하는 함수
    function factiory_movie(title) {
      return
        // 그 객체의 속성이 두 개가 있다. 
       {
        // 첫번째 속성, 메서드
        get_title : function () {
          return title;
        },
        // 두번째 속성, 메서드
        set_title : function(_title) {
          title = _title 
        }
      }
    }
    
    

    객체의 두 개의 메서드가 내부함수이다. 즉 이 내부함수를 생성하고있는 외부함수의 지역변수에 접근할 수 있다는 것이다.

    외부함수의 매개변수 title은 함수안에서 지역변수로 사용된다. 따라서 첫번째 속성인 get_title 메서드가 반환하는 title이 매개변수의 값이 된다.

    두번째 메서드인 set_title 는 매개변수로 _title을 가진다. 이 매개변수를 가지고 title = _title로 바꾸는데 여기서 바뀐 title이 외부함수의 매개변수인 title이 된다.

    ghost = factory_movie('Ghost in the shell');
    matrix = factory_movie('Matrix');
    

    위에서 처럼 함수를 호출했다면 ghost와 matrix에는 똑같이 외부함수에서 반환하는 객체를 담고있지만 두 객체가 접근할 수 있는 title의 값이 각 각 ‘Ghost in the shell’과 ‘Matrix’로 다르다.

    alert(ghost.get_title());
    // 'Ghost in the shell' 호출
    
    alert(matrix.get_title());
    // 'Matrix' 호출
    

    즉 title이라는 외부함수의 지역변수가 서로 다르다는걸 알 수 있다.

    ghost.set_title('공각기동대');
    

    따라서 두번째 메서드를 통해서 _title로 title을 바꾸어 주면 외부함수의 지역변수 title이 바뀌게 되고 첫번째 함수를 통해 바뀐 값으로 반환하게 된다.

    alert(ghost.get_title());
    // '공각기동대' 호출
    
    alert(matrix.get_title());
    // 그대로 'Matrix' 호출
    

    정리
    똑같은 외부함수 factory_movie를 공유하고 잇는 ghost와 matrix의 get_title 결과는 서로 다르다. 즉 외부함수가 실행될 때 마다 새로운 지역변수를 포함하는 클로저가 생성되기 때문이며 ghost와 matrix는 서로 완전히 독립된 객체가 되는 것이다.

    이렇게 메서드를 통해서만 title의 값을 수정하거나 열람할 수 있게 만들어 외부로부터 접근을 차단해서 보안성을 높여줄 수 있는 클로저의 기능이다.


클로저 예제2

클로저에서 자주 언급되는 예제이다.

// 배열
var arr = []
for (var i = 0; i < 5; i++) {
  // 배열에 저장된 함수를 호출
  arr[i] = function() {
    // 함수가 정의되는 시점에서 i를 반환
    return i;
  }
}
// 배열의 값 하나씩 호출
for (var index in arr) {
  console.log(arr[index] ());
}

기대되는 결과는 for in을 통해서 0 ~ 4가 출력되는 것이다.

하지만 실제로 실행해보면 5가 다섯번 출력되는걸 볼 수 있다.

그 이유는 외부함수 for문의 i값이 내부함수에서 접근하는 지역변수가 이나리 var로 선언된 전역변수이기 때문에 동작마다 i값이 갱신되면서 전체에 영향을 주고 마지막 반복 후 i가 5가되면서 반복문이 종료되기 때문에 배열에는 5가 5개 들어있게된다.

해결방법
외부함수를 하나 더 만들어 매개변수를 지역변수로 만든다.

var arr = []
for (var i = 0; i < 5; i++) {
  // id는 내부함수가 접근할 수 있는 외부함수의 지역변수가 된다.
  arr[i] = function(id) {
    return function() {
      // 외부함수의 지역변수 접근
      return id;
    }
    // 외부함수 바로 호출, 인자 i로 받음
  } (i);
}

또다른 방법은 전역변수로 선언된 i 자체를 var가 아닌 let으로 선언해서 지역변수로 만들어주는 방법이 있다.

var arr = []
for(let i = 0; i < 5; i++){
    arr[i] = function(){
        return i;
    }
}
for(var index in arr) {
    console.log(arr[index]());
}