티스토리 뷰

자바스크립트로 코딩을 한다거나 자바스크립트를 공부하는 사람들이라면 누구나 한번쯤 원인모를 에러(사실 지극히 정상인데 본인이 그렇게 느끼게 됨)에 몇시간씩 낭비하는 경험이 있을것이다. 바로 자바스크립트의 클로저(closure)때문이다. 처음 클로저라는 단어를 들으면 아마 closure 보다는 closer를 먼저 떠올리지 않을까 한다. 하지만 closer 가 아니라 closure이기때문에 굳이 제목에서부터 영어를 사용하게됐다(은근히 뜻이 비슷해서 서로 다른 단어를 연상하면서도 말이 통하는 경우가있는건 함정).

 

클로저에 대한 정의는 그 영단어의 뜻을 알아보는것이 가장 빠르다. 가까운 포털을 이용해보자. 클로저라는 단어에 대한 뜻을 살펴보자 '폐쇄' 이다. 그럼 무엇이 폐쇄됐다는 걸까?

 

잠시 함수에 대해 생각해보자. 자바의 메서드와는 다른 함수의 특성과 지난 변수스코프 포스팅을 통해 알게된 변수스코프의 특성에 대해 하나씩만 되짚어보자.

 

1. 함수는 함수와 객체를 반환할 수 있다.

2. 변수 스코프는 렉시컬한 특성을 지닌다.

 

이것이 클로저의 발동 조건이다. 무슨 얘기인지 알아보자. 자바스크립트의 함수가 메서드의 역할을 할때는 자바의 메서드와 거의 유사하게 진행된다. 스택구조로 실행되며 실행이 종료되면 해당 함수는 스택에서 지워지게 된다. 하지만 자바의 메서드와는 다르게 함수는 값으로도 사용되기때문에 함수가 함수를 반환하는것 역시 그리 신기한 일만은 아니다. 그럼 아래 소스를 보자.

 

function outer(){

  var num = 10;

  return function(){

    return ++num;

  }

}

 

var closureTest = outer();

alert(closureTest());

alert(closureTest());

 

outer() 함수를 호출해 그 반환 값을 closureTest 변수에 담았다. 그런데 outer() 함수는 익명 함수를 반환하고있다. 그렇다면 closureTest에는 해당 익명함수가 저장될것이다. 그리고 그 아래에는 함수변수가 된 closureTest() 를 호출하고있다. 그 반환값을 alert() 으로 출력하고있으니 뭔가가 출력이 될것이다. 뭘 출력하게될까? 해당 익명함수가 정의되어있는 outer() 함수를 다시 살펴보자.

outer() 함수는 지역변수로 num 을 정의하고있으며 outer() 함수가 반환하는 익명함수는 num을 반환하고있다. 변수스코프체인을 토대로 생각해봤을때 익명함수에는 num 변수가 없으므로 상위 스코프인 outer() 함수에서 num 을 찾을것이다.

위 소스를 실행시켜보면 내가 alert() 을 2개를 호출하고있기때문에 alert() 창이 2개가 뜰것인다. 그 값은 어떻게 되겠는가?

 

일반적인 함수의 호출은 해당 함수가 호출이 되고 내부 소스를 모두 실행한 다음 return문이 있으면 반환 값을 던진 후, 없으면 그대로 종료되기 마련이다. 그리고 스택에서 제거되는데 현재 소스는 outer()가 종료되면서 반환하는 익명 함수가 변스스코프체인을 통해 outer()의 지역변수를 붙잡고 있음으로서 해당 함수가 종료되지못하게 하고있는 것이다. outer()는 종료되지도, 메모리에서 제거되지도 못한채 자신의 지역변수가 참조되고있기때문에 계속 메모리에 남게되고 outer()의 지역변수인 num은 종료되지 못하고 갇혀버린 메모리에 남아있게된다. 이것이 클로저의 기본적인 발생 원리이다.

 

갇혀버린 메모리에 대한 직접접근은 할 수 없게되며 항상 클로저 함수를 통해 이용해야 된다.

그럼 이런 클로저는 언제 사용하면될까?

 

1. private을 지원하지않는 자바스크립트의 private property

자바는 객체의 속성에 대한 직접접근을 막기위해 private이라는 접근제어자를 지원하고있다. 하지만 언어차원에서 접근제어자를 지원하지않는 자바스크립트는 개발자가 직접 구현해야하는데 이럴때 클로저를 사용하면 손쉽다.

 

function makeObject(){

  var name;

  var age;

 

  return {

    setName : function(_name){

      name = _name;

    },

    getName : function(){

      return name;

    }

  }

}

 

var obj = makeObject();

 

이런식으로 객체를 생성하게되면 name과 age라는 속성에는 직접적으로 접근할 수가 없게된다.

 

2. 전역변수 선언 없이 전역적으로 사용

함수 내부에 함수가 존재할 수도있고 변수스코프체인을 통해 두단계 세단계 상위 변수도 탐색할수있게 되며 실수로 var 없이 선언하게되면 의도치않은 전역변수로 등록되는등 전역에 대한 리스크가 자바보다 훨씬 큰 편이기때문에 자바스크립트에서 어지간하면 전역변수를 사용하지않으려는 사람들이 있다. 하지만 이 함수 저함수에서 갖다쓰는 변수의 경우 전역적으로 선언하지않을 수가 없는데 이런경우 클로저를 이용해 데이터를 보호할 수 있다.

 

var bucket = (function(cnt){

  var count = cnt;

 

  return function(){

    return count;

  }

})(cnt);

 

bucket();

 

3. 싱글톤패턴

한 어플리케이션에서 한 생성자는 한개의 객체만을 생성하는 패턴이 싱글톤 패턴이다.

자바에서는 캡슐화와 마찬가지로 생성자를 private으로 지정함으로서 구현이 쉬운편인데 자바스크립트는 그것역시 직접 구현해야한다.

 

function getInstance(name, age){

  var obj;

  function Const(_name, _age){

    this.name = _name;

    this.age = _age;

  }

 

  return (function(){

    if(!obj){

      obj = new Const(name, age);

    }

 

    return obj;

  })();

}

 

var obj = getInstance("LichKing", 27);

 

엉성한 예제이긴 하지만 다음의 3개의 예제를 통해 내가 전하고 싶은 말은 클로저는 그 영단어의 뜻에 걸맞게 개발자조차 접근할 수 없는 폐쇄된 공간에 수거되지못하고 남아있는 메모리를 뜻하기때문에 주로 자바의 private 과같은 접근제어자가 필요할때 사용된다. 널리 사용되고있는 jQuery 같은 라이브러리들도 클로저를 적극 이용하고있으니 단순히 클로저란 무엇인가 에서 그치지말고 데이터보호가 필요할땐 주저없이 클로저를 이용할 수 있도록 익숙해지는것이 중요하다.

다만 클로저의 단점으로는 폐쇄된곳에 수거되지못하고 메모리에 계속 상주해 있는것이다보니 메모리 효율적으로는 좋지 못한 기법이다. 꼭 필요하고, 차라리 1개만 생성해 계속 상주시키는게 나을정도로 자주 호출(생성)되는 객체나 함수일때 사용하게 될것이다.

 

클로저를 어느정도 이해하고 나서도 사실 나중에 클로저 예제소스를 보면 순간적으로 이해가 안되고 헷갈릴때가 있다. 나도 많이 그랬었는데 클로저를 보다 쉽게 이해할수 있는 문구를 생각해냈다. 결국 변수스코프체인을 통해 상위 함수, 객체로 올라갈때 그 값이 전달되는것이 아니라 참조를 한다고 생각하면 이해가 쉽다.

 

function outer(){

  var num = 10;

  return function(){

    return ++num;

  }

}

 

이때 ++num에 10이 들어간다고 생각하면 죽었다 깨어나도 이해하기가 쉽지 않은듯 싶다. ++num에서 num이 상위에 정의되어있는 num을 참조하고있다고 이해하면 좀 쉬운듯한건 나만의 느낌일 수도있겠으나 혹여 도움이 될수도 있으니 한번 언급하면서 포스팅을 마치겠다.

댓글
댓글쓰기 폼