11. 클로저 (Closure)
Last updated
Last updated
클로저란 내부 함수가 외부로 반환된 이후에도 life-cycle 이 유지되는 것을 말합니다. 클로저 안에 정의된 함수는 만들어진 환경을 기억합니다.
제가 쓴 글을 한번 읽어보셔도 도움이 될 것 같아요. (미디엄)[https://medium.com/@appear.ko/%EB%A0%89%EC%8B%9C%EC%BB%AC-%EC%8A%A4%EC%BD%94%ED%94%84-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0-ef3c8e8584d4]
위에 콘솔을 찍어주는 print() 함수를 호출합니다. 결과는 olaf 가 찍힙니다. 왜일까요? 저는 처음에 당연히 getName 함수안에서 실행이 되었으니 getName 의 name 을 찍을줄 알았습니다. 이 때 나오는 개념이 렉시컬 스코프입니다. print 라는 함수는 getName의 밖에 선언 되어 있습니다. 글로벌 영역이지요 print() 가 실행이 된다면 자신의 로컬 영역에서 name을 한번 찾을 테고 없으니까 자신이랑 가까운 글로벌 영역으로 올라가 글로벌의 name를 찍는 것 입니다. 쉽게 생각했을때 이처럼 소스코드가 작정되는 시점에 이미 스코프가 정해지는 것을 렉시컬 스코프라고합니다.
outer 는 inner 라는 함수를 리턴한다.
inner 는 생성 당시의 스코프 체인을 기억하고있다.
foo 라는 변수에 outer 의 return 값인 inner (내부에서 외부를 참조하는 스코프 체인을 기억하고있는)를 담았다.
foo() 를 실행한다. 이는 inner() 를 실행하는 것과 같다. inner 는 스코프체인을 기억하고 있기 때문에 outer 의 num 까지 접근이 가능하다. 결과적으로 num 에는 10 이 출력된다.
언제 클로저를 이용할까 ?
private 접근제어: Javascript 에는 일반적으로 접근 제어자라는 값이 없다. private 하게 값의 접근을 제어하고 싶다면 스코프를 이용 할 수 있는데, 이 때 클로저를 사용한다.
클로저를 이용하면 아래와 같이 count 변수를 숨기며 값을 제어 할 수 있도록 만들 수 있다.
함수형 프로그래밍: 사이드이펙트를 일으키지 않는 목적을 가진 함수들의 조합
add 라는 함수는 a 라는 인자를 받아 새로운 b 라는 인자와 더해주는 함수(기존의 a 를 참조한 상태로 리턴된다)를 리턴한다.
우리는 add 라는 함수를 이용하여 어떤 값이 들어오더라도 10 을 더해주는 add10
이라는 함수를 만들 수 있다.
루프 내부의 클로저: 클로저가 가진 스코프 문제
굉장히 유명한 단골 문제라고 생각한다. for 루프를 돌면서 setTimeout 이 돈다. 당연히 해당 setTimeout callback 은 0 의 time 을 가지기 때문에 해당번째의 index 가 찍힐 것 이라는 기대를 가지게한다.
하지만 기대했던 결과와는 다르게 i 에는 6 이라는 값이 찍히게되는데, 쉽게 설명하자면 setTimeout 이 동작하는곳과 for 문이 동작하는 곳이다르다. for 문이 먼저 동작하고 for 의 동작이 끝나면 setTimeout 이 호출된다. 이때는 이미 i 가 6이된 상태이기 때문에 console 에는 6이 출력되는 것이다.
이 문제를 어떻게 해결 할 수 있을까?
가장 간단한 방법은 즉시 실행함수를 이용해 index별로 함수를 실행하여 스코프를 갈라버리는 것이다.
또는 let 을 이용하는 방법이 있다.
let 은 for 문에서 특이하게 동작하는데, 해당 코드가 babel 을 통과하여 transpile 이 된다면, setTimeout 부분을 별도의 함수로 분리하여 아래와 같이 동작이 되도록 변경한다.
12. HttpRequest 를 먼저 보고 다시 돌아오시는 것을 추천드립니다 :)
클로저에 대한 개념을 여기서 더 다루기 보다는 언제 어떻게 쓰이는지를 다루면서 좀 더 이야기를 나눠보는 것이 이해해 빠를거라 생각하여 간단한 예제만 추가하였습니다.
뒤에서 알아볼 module pattern 들을 살펴보면서 왜 클로저를 사용해야하며 무엇이 좋은지를 살펴보겠습니다.