[자바스크립트]클로져(closure)


클로져는 사실 우리들의 코드에 항상 있지만, 잘 눈치채지 못하고 있는 요소 중 하나이다. 평소엔 별 문제 없더라도 모르면 해결하기 어려운 상황에 처하는 경우가 한번씩 생기곤한다.

TL;DR 함수가 속한 스코프를 기억하여 함수가 스코프 밖에서 실행될 때에도 그 스코프에 접근할 수 있게 하는 것.

클로져


클로져의 예를 들어보자.

var a = 32;

function foo() {
  var a = 2;
  function bar() {
    console.log(a);
  }
  return bar;
}

var baz = foo();

baz(); // 2

클로져의 예이다. 당연히 같은 이름의 변수를 생성하지 않는 것이 좋지만, 예시니까 가벼운 마음으로 보자.

bar라는 함수가 그 자체로 반환되었다고 생각한다면 외부의 변수 a에 접근하는 것인지 충분히 헷갈릴 수 있는 요소이다. 실제로는 foo의 스코프가 죽지않고 남아있다. (자바스크립트도 gc(가비지컬렉터)가 있다. 일을 안하는 것이아니라 bar가 참조할 수 있도록 남겨놓은 것이다.)

바로 이 남겨진 스코프가 다른 곳에서 참조할 수 있게 된 것이 클로져이다.

아주 흔한 실수


클로져를 설명할 때 빠지지 않는 예제가 하나 있다. 바로 반복문이다.

for(var i=0;i<5;i++){
  setTimeout( function timer(){
    console.log(i);
  }, i*1000);
}

이 함수의 목적은 아마 0부터 4까지 한번씩 출력하는 것이었을 것이다. 하지만, 이 함수의 결과는 1초마다 5를 5번 출력하고 마치게된다.

이유는 뭘까? 반복문이 끝나고 나서야 타임아웃 함수가 작동한다.(참고로 i*1000이 0으로 바뀐다고해도 마찬가지다.) 그러므로 반복문의 마지막 i값인 5가 5번 출력되는 것이다.

5개의 setTimeout함수가 모두 같은 i를 바라보고있기 때문인데, 이런 경우 해결방법은 뭘까? 어떻게 해결해야 원하는 결과를 낼 수 있을까?

즉시호출함수표현식(IIFE)


for(var i=0; i<5; i++){
  (function(j){
    setTimeout( function timer(){
      console.log(j)
    }, j*1000)
  })(i);
}

문제는 해결됬다. 매 setTimeout 함수 호출시마다 j가 포함된 새 닫힌 스코프를 만드니 이제 원하는대로 출력된다. 그럼 같은 원리를 let으로 구현할 수 있지 않을까? (let은 닫힌 스코프를 만든다.)

블록 스코프


for(var i=0; i<5; i++){
  let j = i;
  setTimeout( function timer(){
    console.log(j);
  },j*1000);
}

이것역시 의도대로 동작한다. 근데 잠깐. 하나 더 보자.


for(let i=0; i<5; i++){
  setTimeout( function timer(){
    console.log(i);
  },i*1000);
}

let이 반복문에서 사용될 때는 한 번만 선언되는 것이 아니라 반복할 때 마다 선언된다. 매번 새로운 값으로 닫힌 스코프를 만들어 낼 수 있다.

내가 봤을 땐 클로져를 이해하고 있고, let도 이해하고 있으며, 훨씬 간결하고 이해가능한 마지막 코드가 가장 아름다워보인다.

마치며


공부를 많이하여 아는 것이 많아지면 이해하기도 쉽고 기능적으로도 온전한 코드를 짤 수 있다. 이게 프로그래머가 공부하는 이유가 아닐까.




© 2017. by isme2n

Powered by aiden